LeetCode题目总结
使用异或,因为两个相同的数异或结果为0,Java中的异或:^
使用快慢指针,因为有环的话快指针一定会追上慢指针
public boolean hasCycle(ListNode head) { if (head == null || head.next == null) { return false; } ListNode slow = head; ListNode fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return false; } slow = slow.next; fast = fast.next.next; } return true; }
二叉树后序遍历迭代实现
先把根节点入栈,然后访问根节点的左子树,直到左子树为空,出栈一个节点,如果这个节点右子树为空或者右子树已经访问过了才访问这个根节点,并把这个节点设为已经访问的,把root设为空使得下一次循环直接出栈下一个;否则将这个结点再次压回栈中,去访问其右子树
Deque<TreeNode> stack = new LinkedList<TreeNode>(); TreeNode prev = null;// 用于记录前一个访问的结点 while (root != null || !stack.isEmpty()) { while (root != null) { stack.push(root); root = root.left; } root = stack.pop(); // 如果右子树为空或者右子树已经访问过了才访问根结点 if (root.right == null || root.right == prev) { res.add(root.val);// 访问 prev = root;// 更新 root = null;// 使得下一次循环直接出栈下一个 } else { // 否则,需要将该结点再次压回栈中,去访问其右子树 stack.push(root);// 再次压回栈 root = root.right;// 访问右子树 } }
旋转数组
先翻转整个数组,然后翻转(0到k对数组长度取模)-1,再翻转k对数组长度取模到最后一个
abcdefg
gfedcba
cdefgba
cdefg
旋转图像
翻转左下角和右上角,再向右翻转
一层层旋转
二维前缀和
pre【i】[j]表示第一个元素到第(i+1,j+1)个元素的矩形和
最长回文串
动态规划:dp【i】【j】根据dp【i+1】【j-1】求出来,i是左边,j是右边,abba(dp[0][3])由bb(dp[1][2])求出来
中心扩散:遍历每一个点向两边扩散
生成合法括号
用回溯剪枝法,左分支生成右括号,右分支生成左括号,右括号数量大于左括号数量都是不合法的
用String就不用恢复现场,因为String每递归一次就生成新的,把旧的丢弃,相当于局部变量
用StringBuffer就要恢复现场,因为StringBuffer相当于全局变量
public List<String> generateParenthesis(int n) { ArrayList<String> list = new ArrayList<>(); backTrack(list,new StringBuffer(),0,0,n); return list; } private void backTrack(ArrayList<String> list, StringBuffer stringBuffer, int left, int right, int max) { // 生成一个有效括号了 if (stringBuffer.length() == 2*max) { list.add(stringBuffer.toString()); } // 左括号数小于最大有效括号数就加左括号 if (left < max) { stringBuffer.append("("); backTrack(list,stringBuffer,left+1,right,max);//做选择 stringBuffer.deleteCharAt(stringBuffer.length()-1); // 走到这步就是回溯恢复现场 } // 左括号数比右括号数大就加右括号 if (left > right) { stringBuffer.append(")"); backTrack(list,stringBuffer,left,right+1,max);//做选择 stringBuffer.deleteCharAt(stringBuffer.length()-1); // 走到这步就是回溯恢复现场 } }
最小路径和
dp:当前的最小路径和等于上面的dp加当前步数或左面的的dp加当前步数,这两个取最小值就相当于当前dp
public int minPathSum(int[][] grid) { int[][] dp = new int[grid.length][grid[0].length]; for (int j = 0; j < grid[0].length; j++) { if (j != 0) { dp[0][j] = dp[0][j-1] + grid[0][j]; } else { dp[0][j] = grid[0][j]; } } for (int j = 0; j < grid.length; j++) { if (j != 0) { dp[j][0] = dp[j-1][0] + grid[j][0]; } else { dp[j][0] = grid[j][0]; } } for (int i = 1; i < grid.length; i++) { for (int j = 1; j < grid[0].length; j++) { dp[i][j] = Math.min(dp[i-1][j]+grid[i][j],dp[i][j-1]+grid[i][j]); } } return dp[grid.length-1][grid[0].length-1]; }
字母异位词分组
方法一:把排序的字符串当做key
方法二:比较字符串每个字符出现的次数,eat和ate的键都为a1e1t1,'a' - 'a'等于0,'a'+0等于a
字符串相加
数字字符和‘0’相减得到数字
解码方法
dp[i]表示字符串0到i的编码数
如果要求前i个字符的解码数
我们可以先求前i-1个字符的解码数,但前提条件是当前字符也可以解码(一个字符的话,只要不是0,都可以) 还可以求前i-2个字符的解码数,但前提条件是当前字符和前一个字符构成的两个数字是有效的。
上楼梯的复杂版?
如果连续的两位数符合条件,就相当于一个上楼梯的题目,可以有两种选法:
1.一位数决定一个字母
2.两位数决定一个字母
就相当于dp(i) = dp[i-1] + dp[i-2];
如果不符合条件,又有两种情况
1.当前数字是0:
不好意思,这阶楼梯不能单独走,
dp[i] = dp[i-2]
2.当前数字不是0,即大于6,只能自己编码
不好意思,这阶楼梯太宽,走两步容易扯着步子,只能一个一个走
dp[i] = dp[i-1];
复原ip地址
相当于用回溯法分割字符串,分割成四段时判断最后一段是否符合
反转链表
要用一个临时变量保存递归函数走到返回的节点
两两交换链表的节点
迭代法:哨兵节点指向第二个节点,第一个节点指向第二个节点的后一个节点,第二个节点指向第一个节点
递归法:遍历到最后一个节点,然后第一个节点执行已经处理的链表,第二个节点指向第一个节点
回文链表
递归法:从尾开始遍历和从头开始遍历的结果一样就是回文链表
反转链表:反转后半部分链表然后和前边部分比较
接雨水
每列能接的雨水是左边最高的墙和右边最高的墙的最小值,然后最小值减去当前列的高度等于每列能接的雨水
双指针法:假设一开始left-1
大于right+1
,则之后right
会一直向左移动,直到right+1
大于left-1
。在这段时间内right
所遍历的所有点maxRight都是小于maxLeft的,所以只需要根据原则判断maxright
与当前高度的关系就行
最接近的三数之和
双指针:先排序,然后算sum和target的距离,用target-sum的绝对值求,距离更小就把结果更新
三数之和
往后遍历时要跳过重复项
每日温度
每遍历一个温度都要和栈顶元素比,比栈顶元素大,就两个下标相减,然后栈顶元素出栈,当前温度入栈,这个栈是单调递减的,后入栈的元素总比栈顶元素小,栈顶元素最小,栈底元素最大,栈顶元素因为是最小的,所以还没找到下一个比当前栈顶元素大的
字符串解码
从内向外生成与拼接字符串,这与栈的先入后出特性对应
3[a2[c]]
遍历到3 muliti = 3
遍历到‘【’,muliti入multi栈,res入res栈,muliti清0,res清0,
遍历到a,res = a
遍历到2,muliti = 2
遍历到‘【’,muliti入multi栈,res入res栈,muliti清0,res清0, multi栈:2 3 res栈:a
遍历到a,res = c
遍历到‘】’,multi栈出栈一个,cur_multi = 2,cur_multi *res = cc,res栈出栈一个和cc连接,因为要拼接上个【到当前【的字符串,res=acc
遍历到‘】’,multi栈出栈一个cur_multi = 3,cur_multi *res = accaccacc
下一个更大的数字
也是用单调栈,当前元素比栈顶元素大就找到比栈顶元素大的下一个元素,
当前元素比栈顶元素小,当前元素入栈,找比当前元素大的下一个元素,
每次都是找比栈顶元素大的下一个元素,只要栈顶元素没出栈就是没找到比栈顶元素大的下一个元素
public int[] nextGreaterElement(int[] nums1, int[] nums2) { int[] res = new int[nums1.length]; Deque<Integer> stack = new LinkedList<>(); Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums2.length; i++) { while (!stack.isEmpty() && nums2[i] > stack.peek()) { //当前元素比栈顶元素大就找到比栈顶元素大的下一个元素 map.put(stack.pop(),nums2[i]); } //当前元素比栈顶元素小,当前元素入栈,找比当前元素大的下一个元素,每次都是找比栈顶元素大的下一个元素 //只要栈顶元素没出栈就是没找到比栈顶元素大的下一个元素 stack.push(nums2[i]); } for (int i = 0; i < nums1.length; i++) { res[i] = map.getOrDefault(nums1[i],-1); } return res; }
二叉搜索树的个数
n=3 时呢? 我们来看 [1,2,3] 如果提起 1 作为树根,左边有f(0)种情况,右边 f(2) 种情况,左右搭配一共有 f(0)*f(2) 种情况 如果提起 2 作为树根,左边有f(1)种情况,右边 f(1) 种情况,左右搭配一共有 f(1)*f(1) 种情况 如果提起 3 作为树根,左边有f(2)种情况,右边 f(0) 种情况,左右搭配一共有 f(2)*f(0) 种情况 容易得知 f(3) = f(0)*f(2) + f(1)*f(1) + f(2)*f(0)
每一项两个 f()
的数字加起来都等于 n-1
dp[i]=∑dp[j]∗dp[i−j−1]
base case 当 n = 0时,没有数字,只能形成一种 BSTBST :空树。 当 n = 1 时,只有一个数字,只能形成一种 BSTBST :单个节点。
f(2)=f(0)*f(1) + f(1)*f(0)
二叉树中的最大路径和
最大路径和:当前节点+左子树最大值+右子树最大值
当前节点+左子树最大值
当前节点+右子树最大值
当前节点
上面四个取最大值
从 dfs(-10) 开始,
1.1 dfs(9):
1.1.1 左孩子为空;贡献为 0
1.1.2 右孩子为空,贡献为 0
1.1.3 更新 res = max (-∞,(9 + 0 + 0)) = 9
1.1.4 返回 dfs(9) = 9 + max(左孩子贡献,右孩子贡献)) = 9
1.2 dfs(20)
1.2.1 dfs(15):
1.2.1.1 左孩子为空;贡献为0
1.2.1.2 右孩子为空,贡献为0
1.2.1.3 更新 res = max(9, 15 + 0 + 0) = 15
1.2.1.4 返回 dfs(15) = 15 + 0 = 15
1.2.2 dfs(7):
1.2.2.1 左孩子为空;贡献为 0
1.2.2.2 右孩子为空,贡献为 0
1.2.2.3 更新 res = max(15, 7 + 0 + 0) = 15
1.2.2.4 返回 dfs(7) = 7 + 0 = 7
这层是dfs(20)
1.2.3 更新 res = max (15, 20 + dfs(15) + dfs(7) ) = 42
1.2.4 返回dfs(20) = 20 + max(15, 7) = 35
这层是dfs(-10)
1.3 更新 res = max(42, -10 + dfs(9) + dfs(20) ) = max(42, 34) = 42
1.4 返回 dfs(-10) = -10 + max(9, 35) = 25 (当然这一步就没啥用了,已经有最终res)
所以最大路径和 res = 42
关键就是区分:
-
当前节点最大路径和计算:以当前节点为起点的所有路径和
-
当前节点对上一层的贡献:只能选择当前节点的最大的一条路径作为贡献,因为路径节点不可重复
根据前序遍历和中序遍历构造二叉树
preorder第一个元素为root,在inorder里面找到root,在它之前的为左子树(长l1),之后为右子树(长l2)。preorder[1]到preorder[l1]为左子树,之后为右子树,分别递归。
654、最大二叉树
每一层递归找数组最大的值构建根节点,然后以最大值的左边数组进入下一层递归,最大值的右边数组进入下一层递归
110、平衡二叉树
//和树的子结构类似,先判断当前子树是否是平衡树,再判断左右子树是否是平衡树,不过这里是与,树的子结构是或
public boolean isBalanced(TreeNode root) { if (root == null) return true; /** abs(maxDepth(root.left)-maxDepth(root.right)) <= 1 :判断 当前子树 是否是平衡树; isBalanced(root.left)判断 当前子树的左子树 是否是平衡树 isBalanced(root.right)判断 当前子树的右子树 是否是平衡树 */ return Math.abs(maxDepth(root.left)-maxDepth(root.right)) <=1 && isBalanced(root.left) && isBalanced(root.right); } public int maxDepth(TreeNode root) { //root为空即到达叶子节点的下一个节点 if (root == null) return 0; return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1; }
二叉树的最近公共祖先
递归函数TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
的作用是从当前根节点向下搜索 p、q是否存在于当前节点的左右子树中,找到就返回相应的节点。存在p不存在q就返回p,存在q不存在p就返回q
//p和q始终是不变的,root是变化的 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { //找到了q或p节点或者跨过了叶子节点 if(root == null || root == p || root == q) return root; TreeNode l_res = lowestCommonAncestor(root.left,p,q); TreeNode r_res = lowestCommonAncestor(root.right,p,q); //如果在当前节点的左右子树中分别找到了给定了两个节点,则当前节点就是最近的公共祖先 if(l_res != null && r_res != null) return root; //在左右子树中搜索的结果如果不是均不为null,说明给定的两个节点中存在包含关系,即一个节点在另一个节点的子树中,这时最近的公共祖先就是不为null的那个节点 return l_res == null ? r_res : l_res; }
二叉树展开成链表
1
/ \
2 5
/ \ \
3 4 6
/**将原来的右子树接到左子树的最右边节点
* 2
* / \
* 3 4
* \
* 5
* \
* 6
*/
/**原来的左子树接到右子树
* 1
* \
* 2
* / \
* 3 4
* \
* 5
* \
* 6
*/
岛屿数量
遇到1然后就向上下左右扩散,遍历过的1变成0,然后再找下一个1
有序数组转二叉搜索树
把有序数组的中间数作为根节点,然后中间数的左边进入下一层递归,返回的值作为上一层递归根节点的左子树,中间数的右边进入下一层递归,返回的值作为上一层递归根节点的右子树
-10,-3,0,5,9
统计一个数字在排序数组中出现的次数