51.22.括号生成
字符串回溯的典型问题
char[] path;
List<String> res;
int n;
public List<String> generateParenthesis(int n) {
this.n = n;
path = new char[2*n];
res = new ArrayList<>();
dfs(0,0,0);
return res;
}
public void dfs(int index,int left, int right){
if(index == 2*n){
res.add(new String(path));
return ;
}
if(left < n){
path[index] = '(';
dfs(index + 1,left + 1 , right);
}
if(right < left){
path[index] = ')';
dfs(index + 1, left, right + 1);
}
}
52.49. 字母异位词分组 - 力扣(LeetCode)
本题考查哈希思想和字符串的处理
I排序
使用排序后的字符数组作为key,异位词的key相同,使用hashTable将词分组。
注意这里需要用String作为key ,而不能用char数组作为key,原因在于String的equals方法已经实现了基于内容的比较,而char数组的equals方法仍然是基于引用的比较。
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>> map = new HashMap<>();
for (String str : strs) {
char[] arr = str.toCharArray();
Arrays.sort(arr);
String key = new String(arr);
List<String> list = map.getOrDefault(key, new ArrayList<>());
list.add(str);
map.put(key,list);
}
return new ArrayList<List<String>>(map.values());
}
时间复杂度O(n*mlogm)
空间复杂度O(n*m)
II统计各个字符串字母个数作为key,进行分组
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>> map = new HashMap<>();
for (String str : strs) {
int[] cnt = new int[26];
for(int i = 0 ; i < str.length() ; i++){
cnt[str.charAt(i) - 'a']++;
}
StringBuilder key = new StringBuilder();
for(int i = 0 ; i < 26 ; i++){
if(cnt[i] != 0){
key.append(i + 'a');
key.append(cnt[i]);
}
}
List<String> list = map.getOrDefault(key.toString(),new ArrayList<>());
list.add(str);
map.put(key.toString(),list);
}
return new ArrayList<List<String>>(map.values());
}
53.48. 旋转图像 - 力扣(LeetCode)
I 使用辅助数组
原来图像中第i行第j个的像素,在旋转后,它出现在倒数第 i 列的第 j 个位置。
空间复杂度O(n^2)
时间复杂度O(n^2)
public void rotate(int[][] matrix) {
int[][] newMatrix = new int[matrix.length][matrix[0].length];
for(int i = 0 ; i < matrix.length ; i++){
for(int j = 0 ; j < matrix[0].length ; j++){
newMatrix[j][matrix[0].length - i - 1] = matrix[i][j];
}
}
for(int i = 0 ; i < matrix.length ; i++){
for(int j = 0 ; j < matrix[0].length ; j++){
matrix[i][j] = newMatrix[i][j];
}
}
}
II原地旋转
题目中要求我们尝试在不使用额外内存空间的情况下进行矩阵的旋转,也就是说,我们需要「原地旋转」这个矩阵。那么我们如何在方法一的基础上完成原地旋转呢?
public void rotate(int[][] matrix) {
int row = 0 ,col = 0;
int length = matrix.length;
if(matrix.length % 2 == 0){
row = matrix.length/2;
col = matrix.length/2;
}else{
row = (matrix.length + 1)/2;
col = (matrix.length - 1)/2;
}
for(int i = 0 ; i < row ; i++){
for(int j = 0 ; j < col ; j++){
int temp = matrix[i][j];
matrix[i][j] = matrix[length - 1 - j][i];
matrix[length - 1 - j][i] = matrix[length - 1 - i][length - 1 - j];
matrix[length - 1 - i][length - 1 - j] = matrix[j][length - 1 - i];
matrix[j][length - 1 - i] = temp;
}
}
}
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
// 水平翻转
for (int i = 0; i < n / 2; ++i) {
for (int j = 0; j < n; ++j) {
swap(matrix[i][j], matrix[n - i - 1][j]);
}
}
// 主对角线翻转
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
swap(matrix[i][j], matrix[j][i]);
}
}
}
};
54.46. 全排列 - 力扣(LeetCode)
关于数组的回溯问题
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
boolean[] visited;
public List<List<Integer>> permute(int[] nums) {
visited = new boolean[nums.length];
dfs(nums);
return res;
}
public void dfs(int[] nums){
boolean isAns = true;
for(int i = 0 ; i < nums.length ; i++){
if(!visited[i]){
isAns = false;
break;
}
}
if(isAns){
res.add(new ArrayList<>(list));
return ;
}
for(int i = 0 ; i < nums.length ; i++){
if(visited[i] == false){
visited[i] = true;
list.add(nums[i]);
dfs(nums);
visited[i] = false;
list.remove(list.size() - 1);
}
}
}
这里用到visited数组进行剪枝和去重,也可以用set:
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int[] nums;
Set<Integer> set = new HashSet<>();
public List<List<Integer>> permute(int[] nums) {
this.nums = nums;
dfs();
return res;
}
public void dfs(){
if(path.size() == nums.length){
res.add(new ArrayList<>(path));
return ;
}
for(int i = 0 ; i < nums.length ; i++){
if(!set.contains(nums[i])){
set.add(nums[i]);
path.add(nums[i]);
dfs();
path.remove(path.size()-1);
set.remove(nums[i]);
}else{
continue;
}
}
}
55.42. 接雨水 - 力扣(LeetCode)
I前后缀
时间复杂度O(n)
空间复杂度O(n)
public int trap(int[] height) {
int[] leftMax = new int[height.length];
int[] rightMax = new int[height.length];
int res = 0;
for(int i = 1 ; i < height.length ; i++){
leftMax[i] = Math.max(height[i-1],leftMax[i-1]);
}
for(int i = height.length - 2 ; i >= 0 ; i--){
rightMax[i] = Math.max(height[i+1],rightMax[i+1]);
}
for(int i = 0 ; i < height.length ; i++){
int water = Math.min(leftMax[i],rightMax[i]) - height[i];
if(water > 0){
res += water;
}
}
return res;
}
II单调栈
public int trap(int[] height) {
int res = 0;
Deque<Integer> stack = new ArrayDeque<>();
stack.push(0);
for(int i = 1 ; i < height.length ; i++){
if(height[stack.peek()] == height[i]){
stack.pop();
}else if(height[stack.peek()] < height[i]){
while(!stack.isEmpty() && height[stack.peek()] < height[i]){
int mid = stack.pop();
if(!stack.isEmpty()){
int left = stack.peek();
int right =i;
int w = right - left - 1;
int h = Math.min(height[right],height[left]) - height[mid];
res += w*h;
}
}
}
stack.push(i);
}
return res;
}
56.39. 组合总和 - 力扣(LeetCode)
回溯问题 dfs
其中为了避免因为顺序导致的List不同,先将candidates进行排序,按排序后的进行计算。
并且由于判断递归进入的逻辑sum + candidates[i] <= target 一项,Arrays.sort(candidates)也是必须的。
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int[] candidates;
int target;
int sum = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
this.candidates = candidates;
this.target = target;
dfs(0);
return res;
}
public void dfs(int startIndex){
if(sum == target){
res.add(new ArrayList<>(path));
}
for(int i = startIndex ; i < candidates.length && sum + candidates[i] <= target ; i++){
sum += candidates[i];
path.add(candidates[i]);
dfs(i);
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
57.543. 二叉树的直径 - 力扣(LeetCode)
回溯问题
int res;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return res;
}
public int dfs(TreeNode root){
if(root.left == null && root.right == null){
return 1;
}
int leftHeight = 0;
if(root.left != null){
leftHeight = dfs(root.left);
}
int rightHeight = 0;
if(root.right != null){
rightHeight = dfs(root.right);
}
res = Math.max(res,leftHeight + rightHeight);
return Math.max(leftHeight,rightHeight) + 1;
}
58.34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
二分法
public int[] searchRange(int[] nums, int target) {
int left = lowerBound(nums,target);
int right = lowerBound(nums,target + 1) - 1;
if(left == nums.length || nums[left] != target){
return new int[]{-1,-1};
}
return new int[]{left,right};
}
public int lowerBound(int[] nums,int target){
int left = 0 , right = nums.length - 1;
int mid = left + (right - left)/2;
while(left <= right){
mid = left + (right - left)/2;
if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
return left;
}
59.33. 搜索旋转排序数组 - 力扣(LeetCode)
仍然是二分法,需要掌握二分法的思想,具体分析
public int search(int[] nums, int target) {
int n = nums.length;
if(n == 1){
return nums[0] == target ? 0 : -1;
}
int left = 0 ,right = n - 1;
while(left <= right){
int mid = left + (right - left)/2;
if(target == nums[mid]){
return target;
}
if(nums[0] <= nums[mid]){
//左侧有序
if(nums[0] <= target && target <= nums[mid]){
//在左侧,到左侧找
right = mid - 1;
}else{
left = mid + 1;
}
}else{
//右侧有序
if(nums[mid] <= target && target <= nums[n - 1]){
//在右侧,到右侧找
left = mid + 1;
}else{
right = mid - 1;
}
}
}
return -1;
}
60.32. 最长有效括号 - 力扣(LeetCode)
I栈
大多数人对于这题的第一直觉是找到每个可能的子串后判断它的有效性,但这样的时间复杂度会达到 O(n^3),无法通过所有测试用例。但是通过栈,我们可以在遍历给定字符串的过程中去判断到目前为止扫描的子串的有效性,同时能得到最长有效括号的长度。
这里栈底的“最后一个没有被匹配的右括号下标”即左侧边界。
public int longestValidParentheses(String s) {
Deque<Integer> stack = new ArrayDeque<>();
stack.push(-1);
int res = 0 ;
for(int i = 0 ; i < s.length() ; i++){
if(s.charAt(i) == '('){
stack.push(i);
}else{
stack.pop();
if(stack.isEmpty()){
stack.push(i);
}else{
res = Math.max(res,i - stack.peek());
}
}
}
return res;
}
II动态规划
1.dp数组及下标含义
dp[i] : 以s.charAt(i)为结尾的最长有效字符串长度为dp[i]
2.递推方程
(1)考虑累加
s.charAt(i) == ')' && s.charAt(i) == '('
那么 dp[i] = dp[i-2] + 2;
(2)考虑嵌套
s.charAt(i) == ')' && s.charAt(i-1) == ')'
如果是有效字符串,那么在i-1位置的‘)'匹配的'('之前,必然有一个'(' 与i位置的')'相匹配
即 s.charAt(i - dp[i-1] - 2) == '('
那么 dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2];
3.初始化
初始化为0
4.遍历顺序
从前向后
5.举例推导dp数组
public int longestValidParentheses(String s) {
int[] dp = new int[s.length()];
int res = 0;
for(int i = 1 ; i < s.length() ; i++){
if(s.charAt(i) == ')'){
if(s.charAt(i-1) == '('){
dp[i] = (i >= 2 ? dp[i-2] : 0) + 2;
}else if(i - dp[i-1] > 0 && s.charAt(i - dp[i-1] - 1) == '('){
dp[i] = dp[i-1] + 2 + ((i - dp[i-1] )>= 2 ? dp[i-dp[i-1]-2] : 0 ) ;
}
res = Math.max(res,dp[i]);
}
}
return res;
}