《剑指offer》| 刷题小记

写在前面

寒假的时候粗略的刷过一遍,但是仍有一些不太理解,而且忘的差不多了…
在秋招前再刷一遍QAQ
记录一下
动态规划详解:帅地知乎讲解

2022-5-9

剑指09:两个栈实现队列

  • key:“两个薯片桶来回倒”
  • 知识点:双端队列deque实现栈与队列

《剑指offer》| 刷题小记_第1张图片

class CQueue {
    Deque<Integer> stack1 = null;
    Deque<Integer> stack2 = null;
    public CQueue() {
        stack1 = new LinkedList<Integer>();
        stack2 = new LinkedList<Integer>();
    }
    
    public void appendTail(int value) {
        stack1.push(value);
    }
    
    public int deleteHead() {
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
        if(stack2.isEmpty()){	//注意此处的情况:队列为空
            return -1;
        }else{
            return stack2.pop();
        }
    }

}

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue obj = new CQueue();
 * obj.appendTail(value);
 * int param_2 = obj.deleteHead();
 */

剑指30:含min函数的栈

  • key:另一个顺次存放小值,用来完成min函数,和stack1有一个同步关系
class MinStack {
    Deque<Integer> stack1 = null;
    Deque<Integer> stack2 = null;
    /** initialize your data structure here. */
    public MinStack() {
        stack1 = new LinkedList<Integer>();
        stack2 = new LinkedList<Integer>();
    }
    
    public void push(int x) {
        stack1.push(x);
        if(stack2.isEmpty()||stack2.peek()>=x){ //放前面:判断是否为空;不然stack2.peek()会报错
            stack2.push(x);
        }
    }
    
    public void pop() {
        if(stack1.pop().equals(stack2.peek())){
            stack2.pop();
        }
    }
    
    public int top() {
        return stack1.peek();
    }
    
    public int min() {
        return stack2.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.min();
 */

2022-5-10

剑指06:从头到尾打印链表

  • 辅助栈
  • 递归
/**
辅助栈
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        ListNode p = head;
        Deque<Integer> stack = new LinkedList<Integer>();
        int num=0;
        while(p!=null){
            stack.push(p.val);
            p=p.next;
            num++;
        }
        int[] ans = new int[num];
        int i=0;
        while(stack.peek()!=null){
            ans[i++] = stack.pop();
        }
        return ans;
    }
}
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    ArrayList<Integer>tmp = new ArrayList<Integer>();
    public int[] reversePrint(ListNode head) {
     recur(head);
    int[] ans = new int[tmp.size()];
    for(int i=0;i<ans.length;i++){
    ans[i] = tmp.get(i);
    } 
    return ans;
    }
     void recur(ListNode p){
        if(p==null)return;
        recur(p.next);
        tmp.add(p.val);
    }
}

剑指24:反转链表

  • 迭代(双指针)
    《剑指offer》| 刷题小记_第2张图片

  • 递归
    《剑指offer》| 刷题小记_第3张图片

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
            ListNode cur=head,pre=null;
            while(cur!=null){
                ListNode tmp = cur.next;
                cur.next = pre;
                pre = cur;
                cur = tmp;
            }
            return pre;
    }
}
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        return recur(head,pre);
    }
    ListNode recur(ListNode head,ListNode pre){
	if(head==null)return pre;
	ListNode ans = recur(head.next,head);
	head.next = pre;
	return ans;
}
}

剑指35:复杂链表复制

  • 利用HashMap的映射构建原节点->新节点 遍历再得关系
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head) {
        //哈希表
        Node cur = head;
        Map<Node,Node> map = new HashMap<>();
        //复制结点到新的 存在map中
        while(cur!=null){
            Node node = new Node(cur.val);
            map.put(cur,node);
            cur = cur.next;
        }
        //遍历 构建新的关系
        cur = head;
        while(cur!=null){
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head);
    }
}

2022-5-11

剑指05:替换空格

  • Java中字符串不可更改,新建一个StringBuilder辅助
class Solution {
    public String replaceSpace(String s) {
        StringBuilder sb = new StringBuilder();
        for(char c:s.toCharArray()){
            if(c==' '){
                sb.append("%20");
            }
            else sb.append(c);
        }
        return sb.toString();
    }
}

剑指58:左旋转字符串

  • 字符串切片:subString()
  • 遍历拼接
class Solution {
    public String reverseLeftWords(String s, int n) {
        return s.substring(n, s.length()) + s.substring(0, n);
    }
}
class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder res = new StringBuilder();
        for(int i=n;i<s.length();i++){
            res.append(s.charAt(i));
        }
        for(int i=0;i<n;i++){
            res.append(s.charAt(i));
        }
        return res.toString();
    }
}

2022-5-12

剑指03:数组中的重复数字

  • HashSet遍历
  • 原地交换:每个元素都有自己的位置,冲突则返回
  • 知识点:HashSet使用
class Solution {
    public int findRepeatNumber(int[] nums) {
       HashSet<Integer> ans = new HashSet<>();
       for(int i:nums){
           if(!ans.add(i)){
               return i;
           }
       }
        return 0;
    }
}
class Solution {
    public int findRepeatNumber(int[] nums) {
        int i = 0;
        while(i < nums.length) {
            if(nums[i] == i) {
                i++;
                continue;
            }
            if(nums[nums[i]] == nums[i]) return nums[i];
            int tmp = nums[i];
            nums[i] = nums[tmp];
            nums[tmp] = tmp;
        }
        return -1;
    }
}


剑指53-1:排序数组中查找数字

  • 遍历
  • 二分法+左右遍历

二分法(一)
l=0,r=nums.length; while(l);l=mid+1,r=mid;
二分法(二)
l=0,r=nums.length-1; while(l<=r);l=mid+1,r=mid-1;

class Solution {
    public int search(int[] nums, int target) {
        int l=0,r=nums.length,index=0,ans=0;
       while(l<r){
        int mid = (l+r)/2;
        if(nums[mid]==target){
            index=mid;
            break;
        }else if(nums[mid]<target){
            l=mid+1;
        }else
            r=mid;
       }
        for(int i=index;i<nums.length;i++){
            if(nums[i]==target)
                ans++;
            else{
                break;
            }
        }
        for(int i=index-1;i>=0;i--){
            if(nums[i]==target)
                ans++;
            else{
                break;
            }
        }

        return ans;
    }
}

剑指53-2:缺失数字

  • 二分法:找到元素值与下标不相等的第一个数字
  • 双指针差值:遍历元素,差值不为1的后一个元素,特殊值
class Solution {
    public int missingNumber(int[] nums) {
          int l=0,r=nums.length-1;
          while(l<=r){
            int mid = (l+r)/2;
            if(nums[mid]==mid)l++;
            else{
                r--;
            }
          }
          return l;  
    }
}
class Solution {
    public int missingNumber(int[] nums) {
        int i=0,p=1;
        while(i<nums.length-1){
            p=i+1;
            if(nums[p]-nums[i]!=1)
                return nums[p]-1;
            else{
                i++;
            }
        }
        if(nums[0]==0) return (nums[nums.length-1]+1);
        return 0;
    }
}

2022-5-13

剑指04:二维数组中的查找

  • 条件:从左到右 从上到下 递增
  • 查找从左下角寻找
  • 思想:二分查找;从最左下角 满足“上方小 右方大” 很像二叉树
class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        int i=matrix.length-1,j=0;
        while(i>=0&&j<matrix[0].length){
            if(target==matrix[i][j])return true;
            else if(target>matrix[i][j])j++;
            else i--;
        }
        return false;
    }
}

剑指11:旋转数组的最小数字

  • 数组查数:二分法
  • 最小数字:查找旋转点
    《剑指offer》| 刷题小记_第4张图片
class Solution {
    public int minArray(int[] numbers) {
        int i=0,j=numbers.length-1;
        while(i<j){
            int mid = (i+j)/2;
            if(numbers[mid]==numbers[j]){
                j--;
            }else if(numbers[mid]<numbers[j]){
                j=mid;
            }else i=mid+1;
        }
        return numbers[i];
    }
}

剑指50:第一个只出现一次的字符

  • 两次遍历:一次计数,一次顺序筛查
  • 技巧:全是英文可以通过-‘a’获得数字,桶排序
class Solution {
    public char firstUniqChar(String s) {
        int[] count = new int[26];
        char[] c = s.toCharArray();
        for(int i=0;i<s.length();i++){
            count[(c[i]-'a')]++;
        }
        for(int i=0;i<s.length();i++){
            if(count[c[i]-'a']==1)return c[i];
        }
        return ' ';
    }
}
class Solution {
    public char firstUniqChar(String s) {
        HashMap<Character,Boolean> map = new HashMap<>();
        char[] arr = s.toCharArray();
        for(char c:arr){
            map.put(c,!map.containsKey(c));
        }
        for(char c:arr){
            if(map.get(c))
                return c;
        }
        return ' ';
    }
}

2022-5-14

剑指32:从上到下打印二叉树Ⅰ

  • 层次遍历:广搜BFS循环+队列
  • ArrayList详解
  • 节点加入队列中循环,为空跳出;无需将每一层明确分开
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] levelOrder(TreeNode root) {
        if(root==null)return new int[0];
        Queue<TreeNode> queue = new LinkedList<>(){{add(root);}};  
        // Queue queue = new LinkedList<>(){{ add(root); }};
        ArrayList<Integer> list = new ArrayList<>();
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            list.add(node.val);
            if(node.left!=null)queue.add(node.left);
            if(node.right!=null)queue.add(node.right);
        }
        int[] ans = new int[list.size()];
        for(int i=0;i<list.size();i++){
            ans[i] = list.get(i);
        }
        return ans;
    }
}

剑指32:从上到下打印二叉树Ⅱ

  • 区别:如何控制每一层遍历次数,通过queue.size()确定
  • 最外侧通过!queue.isEmpty()控制循环,内层用queue.size()
    《剑指offer》| 刷题小记_第5张图片
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> q = new LinkedList<>();
        ArrayList<List<Integer>> list = new ArrayList<>();
        if(root!=null)q.add(root);
        while(!q.isEmpty()){
            List<Integer> t = new ArrayList<>();
            for(int i=q.size();i>0;i--){
                TreeNode node = q.poll();
                t.add(node.val);
                if(node.left!=null)q.add(node.left);
                if(node.right!=null)q.add(node.right);
            }
            list.add(t);
        }
        return list;
    }
}

剑指32:从上到下打印二叉树Ⅲ

  • 区别:打印顺序交替变化,通过判断奇偶,双端队列的头插/尾插
  • 判断奇偶可用ans列表的节点数奇偶判断

《剑指offer》| 刷题小记_第6张图片

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        if(root!=null)queue.add(root);
        while(!queue.isEmpty()){
            LinkedList<Integer> list = new LinkedList<>();
            for(int i=queue.size();i>0;i--){
                TreeNode node = queue.poll();
                if(ans.size()%2==0)//是偶数层 从0层开始
                    list.add(node.val);
                else list.addFirst(node.val);
                if(node.left!=null)queue.add(node.left);
                if(node.right!=null)queue.add(node.right);
            }
            ans.add(list);
        }
        return ans;
    }
}

2022-5-15

剑指26:树的子结构

  • 讲解树对称性递归的一文-星晴
  • 一层:遍历A节点,判断当前A是否包含或者换成A的左/右子树是否满足
  • 二层:包含的true条件是B树遍历空,false条件是A树为空 || A.val!=B.val
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if(A==null||B==null)return false;
        //遍历A中的每个结点
        return isContain(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
    }
    public boolean isContain(TreeNode A,TreeNode B){
        if(B==null)return true;
        if(A==null||A.val!=B.val)return false;
        return isContain(A.left,B.left)&&isContain(A.right,B.right);
    }
}

剑指27:二叉树的镜像

  • 注意:需要把左右节点暂存
    《剑指offer》| 刷题小记_第7张图片
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root==null)return null;
        TreeNode tmpleft = mirrorTree(root.right); 
        TreeNode tmpright = mirrorTree(root.left);
        root.left =tmpleft;
        root.right =tmpright;
        return root;
    }
}

剑指28:对称二叉树

  • 一层:特殊值根节点为空,返回true;否者将节点分成左右两个调用二层函数
  • 二层:递归判断两个节点是否相同,同时为空true;有一个为空或者数值不同false
    《剑指offer》| 刷题小记_第8张图片
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        return root==null?true:isCompare(root.left,root.right);
    }
    public boolean isCompare(TreeNode l,TreeNode r){
        if(l==null&&r==null)return true;
        if(l==null||r==null||l.val!=r.val)return false;
        return isCompare(l.left,r.right)&&isCompare(l.right,r.left);
    }
}

2022-5-16

剑指10:斐波那契数列Ⅰ

  • 不能单纯直接递归,会超时
  • 可以通过dp[]配合递归
  • 也可以选择动态规划
class Solution {
    int[] dp = new int[200];
    public int fib(int n) {
        if(n<2)return n;
        int mod = 1000000007;
        if(dp[n]!=0)return dp[n];
        dp[n] = fib(n-1)+fib(n-2);
        dp[n]%=mod;
        return dp[n];
    }
}
class Solution {
    int[] dp = new int[101];
    public int fib(int n) {
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i] = dp[i-1]+dp[i-2];
            dp[i]%=1000000007;
        }
        return dp[n];
    }
}

剑指10:青蛙跳台阶Ⅱ

  • 虽然状态方程同斐波那契数列f(n) = f(n-1)+f(n-2)
  • 主要取决于 最后一次 是跳1个or2个(如下图)
    《剑指offer》| 刷题小记_第9张图片
class Solution {
    int[] dp = new int[105];
    public int numWays(int n) {
        if(n==0)return 1;
        if(n<3)return n;
        int mod = 1000000007;
        if(dp[n]!=0)return dp[n];
        dp[n] = numWays(n-1)+numWays(n-2);
        dp[n]%=mod;
        return dp[n];
    }
}

剑指63:股票的最大利润

  • 动态规划:每一天的最大利润都通过对比更新,cost也是对比更新
  • “数组”——双指针
class Solution {
    public int maxProfit(int[] prices) {
        //动态规划
        //到今天最大的利润值=Math.min(到前一天最大的利润,今天价格-前几天minCost)
        //minCost = Math.min(minCost,今天-1天的价格)
        if(prices.length==0)return 0;
        int cost = prices[0];
        int profit = 0;
        for(int price:prices){
            cost = Math.min(price,cost);
            profit = Math.max(profit,price-cost);
        } 
        return profit;
    }
}
class Solution {
    public int maxProfit(int[] prices) {
        int i=0,j=i+1;
        int max = 0;
        while(j<=prices.length-1){
            int p = prices[j]-prices[i];
            if(p<0)i++;
            else if(p<=max)j++;
            else{
                max=p;
                j++;
            }
        }
        return max;
    }
}

2022-5-17

剑指42:连续子数组的最大和

  • 动态规划:当前的与前一部分有关,dp[i] = Math.max(dp[i-1],0)+nums[i]
  • 数组->“双指针”->滑动窗口(求具体哪一段时使用)
  • 注意:如果前一部分为负数 直接舍弃
class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        int max=nums[0];
        dp[0] = nums[0];
        for(int i=1;i<nums.length;i++){
           //简化写法
           dp[i] = Math.max(dp[i-1],0)+nums[i];
           max = Math.max(max,dp[i]);
        }
        
        return max;
    }
}
class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        int max=nums[0];
        dp[0] = nums[0];
        for(int i=1;i<nums.length;i++){
            if(dp[i-1]>0){
                dp[i] = dp[i-1]+nums[i];
                if(max<dp[i])max=dp[i];
            }else{
                dp[i] = nums[i];
                if(max<dp[i])max=dp[i];
            }
        }
        
        return max;
    }
}

剑指47:礼物最大价值

  • 动态规划:最后位置与Math.max(上,左)有关
class Solution {
    public int maxValue(int[][] grid) {
            int[][] dp = new int[grid.length+1][grid[0].length+1];
            for(int i=0;i<grid.length;i++){
                for(int j=0;j<grid[0].length;j++){
                    dp[i+1][j+1] = Math.max(dp[i][j+1],dp[i+1][j])+grid[i][j];
                }
            }
            return dp[grid.length][grid[0].length];
    }46
}

2022-5-18

剑指:把数字翻译成字符串

  • 动态规划
    《剑指offer》| 刷题小记_第10张图片
class Solution {
    public int translateNum(int num) {
        //动态规划 判断每次的最后两位->是否能满足有两种->10~25
        //dp[i] = dp[i-1]+dp[i-2];
        //dp[i] = dp[i-1]
        String n = String.valueOf(num);
        int[] dp = new int[n.length()+1];
        //dp[0] = dp[1] =1;
        dp[0] = 1;dp[1] = 1;
        for(int i=2;i<=n.length();i++){
            String tmp = n.substring(i-2,i);
            if(tmp.compareTo("10")>=0&&tmp.compareTo("25")<=0){
                dp[i] = dp[i-1]+dp[i-2];
            }else{
                dp[i] = dp[i-1];
            }
        }
        return dp[n.length()];
    }
}

剑指48:最长不含重复字符的子字符串

  • 动态规划
  • 滑动窗口:关键在于 i=Math.max(i,map.get(s.charAt(j))+1); 重点理解“abba”这个特例
关于滑动窗口:链接跳转

《剑指offer》| 刷题小记_第11张图片
如果不理解,可以换一种《剑指offer》| 刷题小记_第12张图片

滑动窗口

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if (s.length()==0) return 0;
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        int max = 0;
        int left = 0;
        for(int i = 0; i < s.length(); i ++){
            if(map.containsKey(s.charAt(i))){
                left = Math.max(left,map.get(s.charAt(i)) + 1);
            }
            map.put(s.charAt(i),i);
            max = Math.max(max,i-left+1);
        }
        return max;
        
    }
}

2022-5-19

剑指18:删除链表

  • 双指针快慢链表
  • 特殊情况:删除头
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        ListNode p=head,q=head.next;
        -
        if(p.val==val)return q;
        while(q!=null){
            if(q.val==val){
                p.next = q.next;
                return head;
            }
            p=q;
            q=q.next;
        }
        return head;
    }
}

剑指22:链表中倒数第k个节点

  • 快慢指针:两者相差k个
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        //快慢指针
        ListNode s=head,f=head;
        for(int i=0;i<k;i++) f=f.next;
        while(f!=null){
            s=s.next;
            f=f.next;
        }
        return s;
    }
}

2022-5-20

剑指25:合并两个排序的链表

  • 新建一个头节点,双指针比较着遍历
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
       ListNode l = new ListNode(0);
       ListNode cur = l;
        while(l1!=null&&l2!=null){
            if(l1.val<l2.val){
                cur.next = l1;
                l1 = l1.next;
            }else{
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        if(l1!=null) cur.next = l1;
        if(l2!=null) cur.next = l2;

       return l.next;
    }
}

剑指52:两个链表的第一个公共节点

  • “你的名字”相遇题:我走过你走的路,你走过我走的路,我们终会相遇
  • 出口:两个指针节点是否相同; 如果走到终点就换成对方的起点
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1=headA,p2=headB;
        while(p1!=p2){
        p1 = p1!=null?p1.next:headB;
        p2 = p2!=null?p2.next:headA;
        }
        return p1;
    }
}

2022-5-21

剑指21:调整数组顺序使奇数位于偶数前面

  • 注意:遍历时边界的问题,通过一端遍历,另一端控制边界
class Solution {
    public int[] exchange(int[] nums) {
            int p=0,q=nums.length-1;
            while(p<q){
                while(nums[p]%2!=0&&p<q)p++;//找偶数 并且防止越界
                if(nums[q]%2!=0){//是奇数
                    int tmp = nums[q];
                    nums[q] = nums[p];
                    nums[p] = tmp;
                    p++;
                }
                    q--;
            }
            return nums;
    }
}

剑指57:和为s的两个数字

  • 题目已经排好序,直接双指针
  • 特殊情况:返回空数组
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int i = 0, j = nums.length - 1;
        while(i < j) {
            int s = nums[i] + nums[j];
            if(s < target) i++;
            else if(s > target) j--;
            else return new int[] { nums[i], nums[j] };
        }
        return new int[0];
    }
}

剑指58:翻转单词顺序

  • 双指针:保证单词头尾
  • trim():去除空格
  • substring():左闭右开
class Solution {
    public String reverseWords(String s) {
        s = s.trim();
        StringBuilder sb = new StringBuilder();
        int i=s.length()-1,j=i;
        while(i>=0){
            while(i>=0&&s.charAt(i)!=' ')i--;//找到下一个空格,单词头
            sb.append(s.substring(i+1,j+1)+' ');
            while(i>=0&&s.charAt(i)==' ')i--;//跳过空格,找到下一个单词尾
            j=i;
        }
        return sb.toString().trim();
    }
}

2022-5-22

传送门:DFS入门教程!!!

剑指12:矩阵中的路径

《剑指offer》| 刷题小记_第13张图片

class Solution {
    public boolean exist(char[][] board, String word) {
        char[] b = word.toCharArray();
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[0].length;j++){
                if(dfs(board,b,i,j,0)==true)
                    return true;
            }
        }
        return false;
    }
    public boolean dfs(char[][] board,char[] b,int i,int j,int k){
        if(i>=board.length||i<0||j>=board[0].length||j<0||board[i][j]!=b[k])return false;
        if(k==b.length-1)return true;//全部通过
        board[i][j] = '\0'; //防止重复
        boolean res = dfs(board,b,i+1,j,k+1)||dfs(board,b,i-1,j,k+1)||dfs(board,b,i,j-1,k+1)||dfs(board,b,i,j+1,k+1);
        board[i][j] = b[k]; //恢复
        return res;
    }
}

剑指13:机器人的运动范围

class Solution {
    public int movingCount(int m, int n, int k) {
        boolean visited[][] = new boolean[m][n];
        return dfs(visited,0,0,m,n,k);
    }

    public int dfs(boolean[][] visited,int i,int j,int m,int n,int k){
        if(i>=m||j>=n||visited[i][j]||cal(i)+cal(j)>k)return 0;
        visited[i][j] = true;
        return 1+dfs(visited,i+1,j,m,n,k)+dfs(visited,i,j+1,m,n,k);
    }
    public int cal(int x){
        int sum =0;
        while(x>0){
            sum+=x%10;
            x/=10;
        }
        return sum;
    }
}

2022-5-23

剑指34:二叉树中和为某一值的路径

《剑指offer》| 刷题小记_第14张图片

  • 某一路径:从头节点到尾节点才算完整的路径
  • 方案:遍历到为 判断一次 回溯
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    LinkedList<Integer> path = new LinkedList<>();
    LinkedList<List<Integer>> res = new LinkedList<>();
    int sum=0;
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        if(root!=null){
            dfs(root,target,sum);
        }
        return res;
    }
    public void dfs(TreeNode root,int target,int sum){
        sum+=root.val;
        path.add(root.val);
        if(root.left!=null)dfs(root.left,target,sum);
        if(root.right!=null)dfs(root.right,target,sum);
        //判断是否满足
        if(sum==target&&root.left==null&&root.right==null)
            res.add(new LinkedList(path));
        //回溯清空
        path.removeLast();
    }

}

```java
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    LinkedList<List<Integer>> res = new LinkedList<>();
    LinkedList<Integer> p = new LinkedList<>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        dfs(root,target);
        return res;
    }
    public void dfs(TreeNode root ,int target){
        if(root==null)return;
        p.add(root.val);
        target-=root.val;
        if(target==0&&root.left==null&&root.right==null)
            res.add(new LinkedList(p));//链表
        dfs(root.left,target);
        dfs(root.right,target);
        p.removeLast();//回溯需要清除
    }
}

剑指36:二叉搜索树与双向链表

  • 二叉搜索树的中序遍历:“左 中 右” 是递增序列
  • 双向循环链表:需要对头尾进行连接

《剑指offer》| 刷题小记_第15张图片

class Solution {
    Node pre, head;
    public Node treeToDoublyList(Node root) {
        if(root == null) return null;
        dfs(root);
        head.left = pre;
        pre.right = head;
        return head;
    }
    void dfs(Node cur) {
        if(cur == null) return;
        dfs(cur.left);
        if(pre != null) pre.right = cur;
        else head = cur;
        cur.left = pre;
        pre = cur;
        dfs(cur.right);
    }
}

剑指54:二叉搜索树的第k大节点

  • 二叉搜索树 中序遍历 倒序:右 根 左 得到递减序列
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    int sum=0,res=0;
    public int kthLargest(TreeNode root, int k) {
        //二叉搜索树 中序遍历 倒序:右 根 左
            dfs(root,k);
            return res;
    }
    public void dfs(TreeNode root,int k){
        if(root==null)return;
        dfs(root.right,k);
        if(++sum==k){
            res=root.val;
            return;
        }
        dfs(root.left,k);
    }
}

2022-5-24

剑指45:把数组排成最小的数

  • 排序:快排模板如下

//快排模板:sort patition swap  从小到大排序

void sort(int[] nums,int start,int end){
	if(start>=end)return;
	int p = patition(nums,start,end);
	sort(nums,start,p-1);
	sort(nums,p+1,end);
}

int partition(int[] nums,int start,int end){
	int p = end; //取的最后一个值
	int i = start;
	for(int j=start;j<end;j++){
		if(nums[j]<nums[p]){ //小到大排序
			swap(nums,i,j);
			i++;
		}
	}
	swap(nums,i,p); //找到标志位了
	return i;
}

void swap(int[] nums,int a,int b ){
	int temp = nums[a];
	nums[a] = nums[b];
	nums[b] = temp;
}
  • 比较: 传送门:CompareTo()方法详解
  • 本题规则:“x”+“y” 比较 “y”+“x”
class Solution {
    public String minNumber(int[] nums) {
        String res = "";
        sort(nums,0,nums.length-1);
        for(int i:nums){
            res = res+i+"";
        }
        return res;
    }
    void sort(int[] nums,int start,int end){
        if(start>=end)return;
        int p = patition(nums,start,end);
        sort(nums,start,p-1);
        sort(nums,p+1,end);
    }
    int patition(int[] nums,int start,int end){
        int p = end;
        int i = start;
        String pv = String.valueOf(nums[p]);
        for(int j=start;j<end;j++){
            String t = String.valueOf(nums[j]);
            if((t+pv).compareTo(pv+t)<0){
                swap(nums,i,j);
                i++;
            }
        }
        swap(nums,i,p);
        return i;
    }
    void swap(int[] nums,int a,int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

剑指61:扑克牌中的顺子

  • 关键点:大小王可以补充成任意牌面,max-min<5,且除了大小王不重复
  • 方法1:Set存储 遍历寻找max min 做差
  • 方法2:排序 + max-min<5
class Solution {
    public boolean isStraight(int[] nums) {
        HashSet<Integer> set = new HashSet<>();
        int min=14,max=0;
        for(int i:nums){
            if(i!=0){
                if(set.contains(i))return false;
                set.add(i);
                min = min<i?min:i;
                max = max>i?max:i;
            }
        }
        return max-min<5?true:false;
    }
}
class Solution {
    //看清题意:大小王可以充当任意牌 意味max-min<5且无重复就行 大小王不用管
    public boolean isStraight(int[] nums) {
        Arrays.sort(nums);
        int min=nums[4],max=nums[4];
        for(int i=0;i<4;i++){
            if(nums[i]==0)continue;
            if(nums[i]==nums[i+1])return false;
            if(nums[i]<min)min=nums[i];
        }
        if(max-min>=5)return false;
        return true;
    }
}

2022-5-25

剑指40:最小的k个数

传送门:K神的排序题解-快排

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        Arrays.sort(arr);
        int[] res = new int[k];
        for(int i=0;i<k;i++){
            res[i] = arr[i];
        }
        return res;
    }

}

剑指41:数据流的中位数

  • 优先队列:本质是小根堆,大根堆则需要改变一下排序规则
  • 优先队列:优先队列详解
    《剑指offer》| 刷题小记_第16张图片

《剑指offer》| 刷题小记_第17张图片

class MedianFinder {
    PriorityQueue<Integer>big;
    PriorityQueue<Integer>small;
    /** initialize your data structure here. */
    public MedianFinder() {
    big = new PriorityQueue<>((n1,n2)->n2-n1);
    small = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        big.add(num);
        small.add(big.poll());
        if(small.size()-big.size()>1)
            big.add(small.poll());
    }
    
    public double findMedian() {
        if(small.size()>big.size())return small.peek();
        return (double)(small.peek()+big.peek())/2;
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

2022-5-26

剑指55: 二叉树的深度Ⅰ

  • 递归:每次在左子树和右子树上选取最大值,并+1
  • 节点为空 return;
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null)return 0;
        
        return 1+Math.max(maxDepth(root.left),maxDepth(root.right));
    }
    
}

剑指55:平衡二叉树Ⅱ

  • 判断左右子树差的时候需要套一层Math.abs()
  • 调用的是上一题的函数
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root==null)return true;
        return (Math.abs(maxD(root.left)-maxD(root.right))<=1)&&isBalanced(root.left)&&isBalanced(root.right);
    }
    public int maxD(TreeNode root){
        if(root==null)return 0;
        return Math.max(maxD(root.left),maxD(root.right))+1;
    }
}

2022-5-27

剑指64:1+…+n

在这里插入图片描述

  • 题目要求不能使用如上
  • 短路来控制递归的终止条件
class Solution {
    public int sumNums(int n) {
        boolean x = (n>1)&&((n+=sumNums(n-1))>0);
        return n;
    }
}

剑指68:二叉搜索树的最近公共祖先Ⅰ

  • 二叉搜索树性质:左小右大
  • 从上到下递归判断:通过作差相乘 判断符号异同
  • 分布两侧 符号异 <=0 此时root就是最近公共父节点
  • 否则根据数值判断 递归左子树/右子树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //二叉搜索树性质 左小右大
        //作差相乘 异侧 符号不同 <=0
        if((root.val-q.val)*(root.val-p.val)<=0)return root;
        return lowestCommonAncestor(root.val>q.val?root.left:root.right,p,q);
    }
}

剑指68:二叉树的最近公共祖先Ⅱ

  • 失去二叉搜索树的特点,需先找到p q点,由底自上递归,left right
  • 当root节点的左右节点是p/q 返回root
  • 若root左右同时为空 返回空
  • root左侧为空 返回右节点;root右侧为空 返回左节点
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //同样看两侧,需递归找到底p q,找不到返回null
        //出口:先找到底
        if(root==null||root==p||root==q)return root;
        //先序 递归 找底
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        //判断 root的左右都不为空 就是root
        if(left==null)return right;
        if(right==null)return left;
        return root;
    }
}

2022-5-28

剑指07:重建二叉树

  • 了解中序和先序的特点
  • 通过回溯法遍历
    《剑指offer》| 刷题小记_第18张图片
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    int[] pre;
    //存放先序节点在中序中的位置 key:先序节点的值 Value:在中序数值中的下标
    HashMap<Integer,Integer> map = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        pre = preorder;
        //初始化
        int index = 0;
        for(int i:inorder){
            map.put(i,index++);
        }
        return recur(0,0,inorder.length-1);
    }
    public TreeNode recur(int root,int left,int right){
        //出口
        if(left>right)return null;
       TreeNode node =  new TreeNode(pre[root]);
       //根节点在中序遍历的位置i
       int i = map.get(pre[root]);
       //在先序遍历root的下一个就是左节点  在中序遍历中左树截止到i
       node.left = recur(root+1,left,i-1);
       //在先序遍历中root后跟左树结束才右节点root+(i-left)+1 在中序遍历中i的后边是右子树
       //i-left是左树的长度
       node.right = recur(root+(i-left)+1,i+1,right);
       return node;
    }
}

剑指16:数值的整数次方

  • 题解:快速幂
class Solution {
    public double myPow(double x, int n) {
        if(x==0)return 0;
        double res=1.0;
        long b = n;
        //分数
        if(b<0){
            x=1/x;
            b=-b;
        }
        while(b>0){
            //从二进制角度看 是1才乘上权重
            if((b&1)==1)
                res*=x;
            //得到每个位置的值
            x*=x;
            b>>=1;
        }
        return res;
    }
}

剑指33:二叉搜索树的后序遍历

  • 后序遍历:左 右 根
  • 递归遍历 每一颗子树

《剑指offer》| 刷题小记_第19张图片

class Solution {
    public boolean verifyPostorder(int[] postorder) {

        return recur(postorder,0,postorder.length-1);
    }
    public boolean recur(int[] postorder,int left,int right){
        //出口
        if(left>=right)return true;
        int p=left;
        //找到左子树的头m-1
        while(postorder[p]<postorder[right])p++;
        int m = p;
        //找到右子树的头p-1
        while(postorder[p]>postorder[right])p++;
        //递归遍历 左子树 右子树 扔掉根节点
        return p==right && recur(postorder,left,m-1)&&recur(postorder,m,right-1);
    }
}

2022-5-29

剑指15:二进制中1的个数

  • 按位运算判断是否为1,注意负数 选择无符号右移
  • ">>>"无符号右移,操作规则:无论正负数,前面补零。
  • ">>"右移 操作规则:正数前面补零,负数前面补1
  • "<<"左移 操作规则:无论正负数,后面补零。
public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int res=0;
        while(n!=0){
            if((n&1)==1)res++;
            n>>>=1;
        }
        return res;
    }
}

剑指65:不用加减乘除法

《剑指offer》| 刷题小记_第20张图片

class Solution {
    //用递归代替加法:(int a,int b)相当于 a+b
    public int add(int a, int b) {
        //出口:进位和=0
        while(b!=0){
            int c = (a&b)<<1; //进位和
            a = a ^ b;  //非进位和
            b = c;
        }
        return a; 
    }
}

2022-5-30

剑指56: 数组中数字出现的次数Ⅰ

  • K神题解
  • 位运算:数字按位做异或计算
  • 异或性质:a异或a==0
  • 有两个数字出现一次
    《剑指offer》| 刷题小记_第21张图片
//hashmap
class Solution {
    public int[] singleNumbers(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i:nums){
            map.put(i,map.getOrDefault(i,0)+1);
        }
        int[] res = new int[2];
        int n=0;
        for(int i:nums){
            if(map.get(i)==1)
                res[n++]=i;
        }
        return res;
    }
}

剑指56:数组中数字出现的次数 II

  • 位运算:K神题解

《剑指offer》| 刷题小记_第22张图片

class Solution {
    public int singleNumber(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i:nums){
            map.put(i,map.getOrDefault(i,0)+1);
        }
        for(int i:nums){
            if(map.get(i)==1)return i;
        }
        return 0;
    }   
}

2022-5-31

剑指39:数组中出现次数超过一半的数字

  • 摩尔投票法:两军作战对冲,相同友军+1,不同敌军-1
  • K神题解
class Solution {
    public int majorityElement(int[] nums) {
        int tmp = 0,count=0;
        for(int i:nums){
            if(count==0){
                tmp = i;
                count++;
            }else if(i!=tmp){
                count--;
            }else if(i==tmp){
                count++;
            }
        }
        return tmp;
    }
}

剑指66:构建乘积数组

  • 难点:不能用除法
    《剑指offer》| 刷题小记_第23张图片
  • 迭代:数组分两次循环 从左到右 从右到左
class Solution {
    public int[] constructArr(int[] a) {
        if(a.length==0)return new int[0];
        int[] b = new int[a.length];
        //迭代:左面 右面
        b[0] = 1;
        for(int i=0;i<a.length-1;i++){
            b[i+1] = b[i]*a[i];
        }
        int tmp=1;
        for(int i=a.length-1;i>0;i--){
            tmp*=a[i];
            b[i-1]*=tmp; 
        }
        return b;
    }
}

2022-6-1

剑指14:剪绳子Ⅰ

  • 数学法/动态规划
  • 动态规划:题解 dp[i]=Math.max( Math.max( j* dp[i-j] , j*(i-j)) , dp[i] )

《剑指offer》| 刷题小记_第24张图片

class Solution {
    public int cuttingRope(int n) {
        //dp[i]:长度为i的绳子 最大乘积为?
        int[] dp = new int[n+1];
        //长度为0 1都不能再分 
        dp[0] = dp[1] = 0;
        for(int i=2;i<=n;i++){//长度为i的绳子
            for(int j=1;j<i;j++){//第一段长度为j,不能超过i
            //内层:第一段分成j 剩下的继续分dp[i-j]  不分(i-j)
                dp[i] = Math.max( Math.max(j*dp[i-j],j*(i-j)) ,dp[i]);
            }
        }
        return dp[n];       
    }
}

剑指57:和为s的连续正数序列Ⅱ

  • 通过sum与target判断
  • 滑动窗口:右指针只能向右走不能回头
  • 优化:
    《剑指offer》| 刷题小记_第25张图片
    《剑指offer》| 刷题小记_第26张图片
class Solution {
    public int[][] findContinuousSequence(int target) {
        //左闭右开
        int i=1,j=1;
        int sum=0;
        List<int[]>res = new ArrayList<>();
        while(i<target){
            if(sum<target){
                sum+=j;
                j++;
            }else if(sum>target){
                sum-=i;
                i++;
            }else{
                int[] tmp = new int[j-i];
                int t=0;
                for(int k=i;k<j;k++){
                    tmp[t++] = k;
                }
                res.add(tmp);
                //优化
                sum-=(i+i+1);
                i+=2;
            }
        }
        return res.toArray(new int[res.size()][]);

    }
}

剑指62:圆圈中最后剩下的数字

  • 约瑟夫环问题:题解跳转
  • 活着的最后角标为0,往回推上一轮n=2 活着的角标
  • f = (f+m)%n
class Solution {
    public int lastRemaining(int n, int m) {
        int res = 0;//胜利者最后角标为0
        for(int i=2;i<=n;i++){//每次推导上一轮 res对应角标 i代表存活者个数
            res = (res+m)%i;
        }
        return res;
    }
}

2022-6-2

剑指29:顺时针打印矩阵

  • 关键点:碰壁换行换列的条件
  • 为空的特殊处理
class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if(matrix.length==0)return new int[0];
        int r = matrix[0].length-1,bottom = matrix.length-1;
        int[] res = new int[(r+1)*(bottom+1)];
        int sum=0,top=0,l=0; 
        while(true){
            for(int t=l;t<=r;t++)res[sum++] = matrix[top][t]; //左到右
            //换行
            if(++top>bottom)break;
            for(int t=top;t<=bottom;t++)res[sum++] = matrix[t][r]; //上到下
            //换列
            if(l>--r)break;
            for(int t=r;t>=l;t--)res[sum++] = matrix[bottom][t];//从右到左
            //换行
            if(top>--bottom)break;
            for(int t=bottom;t>=top;t--)res[sum++] = matrix[t][l];//从下到上
            if(++l>r)break;
        }
        return res;
    }
}

剑指31:栈的压入、弹出序列

  • 通过辅助栈模拟,循环 栈顶与出栈数组匹配,成功则出栈
  • 辅助栈最后若为空 则全部匹配成功
class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Deque<Integer> stack = new LinkedList<>();
        int index=0;
        for(int i:pushed){
            stack.push(i);
            while(!stack.isEmpty()&&stack.peek()==popped[index]){
                stack.pop();
                index++;
            }
        }
        return stack.isEmpty();
    }
}

2022-6-3

剑指20:表示数值的字符串

剑指67:把字符串转换成整数

2022-6-4

剑指59:滑动窗口的最大值Ⅰ

剑指59:队列的最大值Ⅱ

2022-6-5

剑指37:序列化二叉树

剑指38: 字符串的排列

2022-6-6

剑指19:正则表达式匹配

剑指49:丑数

在这里插入图片描述
《剑指offer》| 刷题小记_第27张图片

class Solution {
    public int nthUglyNumber(int n) {
        int[] dp = new int[n];
        dp[0]=1;
        int a=0,b=0,c=0;
        for(int i=1;i<n;i++){
          int tmp = Math.min(Math.min(dp[a]*2,dp[b]*3),dp[c]*5);
          if(tmp==dp[a]*2)a++;
          if(tmp==dp[b]*3)b++;
          if(tmp==dp[c]*5)c++;
          dp[i] = tmp;
        }
        return dp[n-1];
    }
}

剑指60:n个骰子的点数

2022-6-7

剑指17:打印从1到最大的n位数

剑指51:数组中的逆序对

2022-6-8

你可能感兴趣的:(刷题,java,数据结构,算法)