文章目录
124. 二叉树最大路径和——后序遍历+全局变量
- helper函数:是为了得到每个节点的最大贡献值(以该节点为根节点在子树中寻找以该节点为起点的一条路径,使得该路径上的节点值之和最大)
- max=Math.max(max,root.val+left+right):对于二叉树中的一个节点,该节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值,如果子节点的最大贡献值为正,则计入该节点的最大路径和,负就不计入该节点的最大路径和。
- 维护一个全局变量 max 存储最大路径和,在递归过程中更新 max 的值,最后得到的 max 的值即为二叉树中的最大路径和。
class No124 {
int max=Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
helper(root);
return max;
}
//后序遍历,求某个节点的值加上左节点最大长度或者右节点最大长度
public int helper(TreeNode root){
if(root==null) return 0;
int left=Math.max(helper(root.left),0);//小于0就不要这条支路了
int right=Math.max(helper(root.right),0);
max=Math.max(max,root.val+left+right);
return root.val+Math.max(left,right);//这里加上root.val是为了给父节点算节点个数
}
}
234.回文链表——快慢指针,反转链表
- 快慢指针找中间节点slow
- 以slow为头反转链表得到新的链表头newHead(其实就是之前的tail)
- 遍历两个链表看是否是相等的
*/
public class No234 {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
// 找中点,链表分成两个
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 第二个链表倒置
ListNode newHead = reverseList(slow);
// 前一半和后一半依次比较
while (newHead != null) {
if (head.val != newHead.val) {
return false;
}
head = head.next;
newHead = newHead.next;
}
return true;
}
private ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode tail = null;
while (head != null) {
ListNode temp = head.next;
head.next = tail;
tail = head;
head = temp;
}
return tail;
}
}
33.搜索旋转排序数组——二分查找
public class No33 {
/**
* 二分变形,找有序的半边
* @param nums
* @param target
* @return
*/
public static int search(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
int left = 0;
int right = nums.length - 1;
int mid;
while (left <= right) {
mid = (right - left) / 2 + left;
if (nums[mid] == target) {
return mid;
}
//前半部分有序。因此如果 nums[start] <=target<nums[mid],
//则在前半部分找,否则去后半部分找。
if (nums[mid] >= nums[left]) {
//target在前半部分
if (target >= nums[left] && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
//后半部分有序。因此如果 nums[mid] <target<=nums[end],
//则在后半部分找,否则去前半部分找。
if (target <= nums[right] && target > nums[mid]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
}
543.二叉树的直径——后序遍历+全局变量
和124题一样的
public class No543 {
int res = 0;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return res;
}
public int dfs(TreeNode root) {
if (root == null) {
return 0;
}
int left = dfs(root.left);//左子树最大高度
int right = dfs(root.right);//右子树最大高度
res = Math.max(res, left + right);//更新全局变量:当前root的最大长度是left+right
return 1 + Math.max(left, right);
}
}
两个栈实现队列——栈
class MyQueue {
Stack<Integer> stackpop;
Stack<Integer> stackpush;
public MyQueue(){
stackpop = new Stack<>();
stackpush = new Stack<>();
}
public void push(int x){
stackpush.push(x);
}
public void transfer(){
if(stackpop.isEmpty()){
while (!stackpush.isEmpty()){
stackpop.push(stackpush.pop());
}
}
}
public int pop(){
transfer();
if(!stackpop.isEmpty()){
return stackpop.pop();
}
throw new RuntimeException("队列里没有元素");
}
public int peek(){
transfer();
if(!stackpop.isEmpty()){
return stackpop.peek();
}
throw new RuntimeException("队列里没有元素");
}
public boolean empty(){
return stackpush.isEmpty() && stackpop.isEmpty();
}
}
105.前序中序遍历构造二叉树——hashmap,递归
53.最大子序和——dp
dp
public class No53 {
public int maxSubArray_dp(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int max = nums[0];
dp[0] = nums[0];
for (int i = 1; i < n; i++) {
if(dp[i - 1] < 0){
dp[i] = nums[i];
}else{
dp[i] = dp[i - 1] + nums[i];
}
max = Math.max(max,dp[i]);
}
return max;
}
}
dp优化
public class No53 {
public int maxSubArray(int[] nums) {
int ans = nums[0];
int sum = 0;
for (int num : nums) {
if (sum > 0) {
sum += num;
} else {
sum = num;
}
ans = Math.max(sum, ans);
}
return ans;
}
}
94.二叉树中序遍历——莫里斯遍历(重点)
破坏结构的版本
//这个版本会破坏树的结构
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
res.add(cur.val);
cur = cur.right;
} else {
//找左子树的最右的节点,并将当前root接到这个节点的right
TreeNode pre = cur.left;
while (pre.right != null) {
pre = pre.right;
}
pre.right = cur;
TreeNode temp = cur;
cur = cur.left;
temp.left = null;
}
}
return res;
}
不破坏树的原始结构
第二次到达cur的时候进行一次断链
//不破坏结构的莫里斯遍历
public List<Integer> inorderTraversal_v1(TreeNode root) {
List<Integer> res = new ArrayList<>();
TreeNode cur = root;
while (cur != null) {
if (cur.left == null) {
res.add(cur.val);
cur = cur.right;
} else {
//找左子树的最右的节点,并将当前root接到这个节点的right
TreeNode pre = cur.left;
while (pre.right != null && pre.right != cur) {
pre = pre.right;
}
if (pre.right == null) {
pre.right = cur;
cur = cur.left;
}
if (pre.right == cur) {//这是第二次到这里
pre.right = null;//这里断开,那么之前额外建立的右连接会消掉,所以树的结构没有变
res.add(cur.val);
cur = cur.right;
}
}
}
return res;
}
110.平衡二叉树——后序遍历+剪枝
- 后序遍历来判断一个根节点的左右子树的高度差,如果符合平衡就返回正常结果;如果不符合就返回一个标志位-1;
- 如果后序遍历过程中出现了-1,就直接剪枝返回-1;
- 主函数里面判断是不是-1就行了
public class No110 {
//后序遍历,提前返回
public boolean isBalanced(TreeNode root) {
return dfs(root) != -1;
}
public int dfs(TreeNode root) {
if (root == null) return 0;
int left = dfs(root.left);
if (left == -1) return -1;
int right = dfs(root.right);
if (right == -1) return -1;
return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
}
}
20.有效的括号——栈
- 典型的栈:利用后进先出的特性
- 可以先判断奇偶数,奇数直接false
- 遇到前括号就把对应的后括号放到栈里去,
- 然后遇到后括号,弹栈,判断是否相等,当然这个时候如果栈是空的话,就证明后括号的数量比前括号多,也是提前返回false的
public class No20 {
public boolean isValid(String s) {
if (s == null) return true;
if (s.length() / 2 == 1) return false;
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(') {
stack.push(')');
} else if (c == '[') {
stack.push(']');
} else if (c == '{') {
stack.push('}');
} else {
if (stack.isEmpty() || c != stack.pop()) {
return false;
}
}
}
return stack.isEmpty();
}
}
88.合并两个有序数组——尾指针
- 典型的逆序思考,从前面遍历nums1我们可能存在覆盖的情况,但是我们从后往前遍历,每次选最大的放在最后面就不会出现这种情况
public static void merge(int[] nums1, int m, int[] nums2, int n) {
//三指针
int i = m - 1;
int j = n - 1;
int k = m + n - 1;
while (k >= 0) {
if (i < 0) {
nums1[k--] = nums2[j--];
} else if (j < 0) {
nums1[k--] = nums1[i--];
} else {
if (nums1[i] > nums2[j]) {
nums1[k--] = nums1[i--];
} else {
nums1[k--] = nums2[j--];
}
}
}
}
209.长度最小的子数组——sliding window
滑动窗口O(N)
public int minSubArrayLen(int s, int[] nums) {
int n = nums.length;
int i = 0;
int j = 0;
int sum = 0;
int res = Integer.MAX_VALUE;
while(j < n){
sum += nums[j++];
while(sum >= s){
res = Math.min(res, j - i);
sum -= nums[i++];
}
}
return res == Integer.MAX_VALUE ? 0 : res;
}
二分法O(NlogN)
- int[] sums:其中sums[i]表示的是原数组nums前i个元素的和,且sums数组中的元素是递增的。
- 只需要找到sums[k] - sums[j] >= s ,那么 k - j 就是满足的连续子数组,但不一定是最小的
- 可以求sums[j] + s <= sums[k],那这样就好办了,因为数组sums中的元素是递增的,也就是排序的,只需要求出sum[j] + s的值,然后使用二分法查找即可找到这个k
public int minSubArrayLen(int s, int[] nums) {
int n = nums.length;
int min = Integer.MAX_VALUE;
int[] sums = new int[n + 1];
for (int i = 1; i <= n; i++) {
sums[i] = sums[i - 1] + nums[i - 1];
}
for (int i = 0; i <= n; i++) {
int target = s + sums[i];
int index = Arrays.binarySearch(sums, target);
if (index < 0)
index = ~index;
if (index <= n) {
min = Math.min(min, index - i);
}
}
return min == Integer.MAX_VALUE ? 0 : min;
}
141-142.环形链表——双指针
- 链表是否有环
public class No141 {
public static boolean hasCycle(ListNode head){
ListNode slow=head,fast=head;
while (fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow) return true;
}
return false;
}
}
- 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
public class No142 {
/**
* 注意fast的提前返回
* @param head
* @return
*/
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
while (true) {
//没有环提前返回
if (fast == null || fast.next == null) return null;
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
slow = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
113. 路径总和——DFS
写法1
public class No113 {
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<List<Integer>> ans=new ArrayList<>();
if(root==null) return ans;
pathSumHelper(root,sum,new ArrayList<Integer>(),ans);
return ans;
}
private void pathSumHelper(TreeNode root,int sum,ArrayList<Integer> temp,List<List<Integer>> ans){
if(root.left==null&&root.right==null){
if(root.val==sum){
temp.add(root.val);
ans.add(new ArrayList<Integer>(temp));//一定要new一个
temp.remove(temp.size()-1);
//java中的list传递的是引用,所以递归结束后,要把之前加入的元素删除,不要影响到其他分支的temp
}
return;
}
if(root.left==null){
temp.add(root.val);
pathSumHelper(root.right,sum-root.val,temp,ans);
temp.remove(temp.size()-1);
return;
}
if(root.right==null){
temp.add(root.val);
pathSumHelper(root.left,sum-root.val,temp,ans);
temp.remove(temp.size()-1);
return;
}
temp.add(root.val);
pathSumHelper(root.left,sum-root.val,temp,ans);
temp.remove(temp.size()-1);
temp.add(root.val);
pathSumHelper(root.right,sum-root.val,temp,ans);
temp.remove(temp.size()-1);
}
}
写法2
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<List<Integer>> result = new LinkedList<>();
List<Integer> curPath = new LinkedList<>();
recur(result, curPath, root, sum);
return result;
}
private void recur(List<List<Integer>> result, List<Integer> curPath, TreeNode curNode, int sum){
if(curNode == null){
return;
}
curPath.add(curNode.val);
if(curNode.val == sum && curNode.left == null && curNode.right == null){
result.add(new ArrayList<>(curPath));
}else{
recur(result, curPath, curNode.left, sum - curNode.val);
recur(result, curPath, curNode.right, sum - curNode.val);
}
curPath.remove(curPath.size() - 1);
}
}
415.字符串相加
public class No415 {
public String addStrings(String num1, String num2) {
if(num1 == null || num2 == null){
return num1 == null ? num2 : num1;
}
int i = num1.length() - 1;
int j = num2.length() - 1;
StringBuilder res = new StringBuilder();
int a = 0;
int b = 0;
int cur = 0;
int c = 0;
while(i >= 0 || j>= 0){
a = i < 0 ? 0 : num1.charAt(i--) - '0';
b = j < 0 ? 0 : num2.charAt(j--) - '0';
cur = (a + b + c) % 10;
c = (a + b + c) / 10;
res.append(cur);
}
if(c != 0){
res.append(c);
}
return res.reverse().toString();
}
}