Skip to content

剑指Offer


JZ22 链表中倒数最后k个结点

java
public class Solution {
    private int idx;
    public ListNode FindKthToTail (ListNode pHead, int k) {
        if (pHead == null) {
            return null;
        }
        // 这里的newHead继承前一个递归的返回值,上面的几层结果是一样的
        ListNode newHead = FindKthToTail(pHead.next, k);
        if (++idx == k) {
            return pHead;
        }
        return newHead;
    }
}

JZ76 删除链表中重复的结点

java
public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        int flag = 0;
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;
        while (pHead != null) {
            ListNode cur = pHead;
            pHead = pHead.next;
            // 当当前节点后面没有或者与后面不等时,考虑是否加入
            // flag判断是否时第一次遇见
            if (pHead == null || cur.val != pHead.val) {
                if (flag == 0) {
                    tail = tail.next = cur;
                }
                flag = 0;
            } else {
                flag = 1;
            }
        }
        tail.next = null;

        return dummy.next;
    }
}

JZ54 二叉搜索树的第k个节点

java
public class Solution {
    private int ans = -1;
    private int cnt = 0;
    public int KthNode (TreeNode root, int k) {
        // 中序遍历,根据ans的值判断是否早停
        if (root != null) {
            KthNode(root.left, k);
            if (++cnt == k) {
                ans = root.val;
            }
            if (ans == -1) {
                KthNode(root.right, k);
            }
        }
        return ans;
    }
}

JZ33 二叉搜索树的后序遍历序列

java
// 解法一:递归划分一个子树再验证另一棵子树
public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if (sequence.length == 0) {
            return false;
        }
        return verify(sequence, 0, sequence.length - 1);
    }
    public boolean verify(int[] sequence, int l, int r) {
        // 少于两个节点返回true
        if (r - l < 2) {
            return true;
        }
        // 根节点划分区间,先划出左子树,再验证右子树
        int idx = l;
        while (idx < r && sequence[idx] < sequence[r]) {
            idx++;
        }
        for (int i = idx + 1; i < r; i++) {
            if (sequence[i] < sequence[r]) {
                return false;
            }
        }

        // 验证左子树和右子树,根节点[r]不再需要
        return verify(sequence, l, idx - 1) && verify(sequence, idx, r - 1);
    }
}

JZ36 二叉搜索树与双向链表

java
public class Solution {
    private TreeNode head;
    private TreeNode pre;
    public TreeNode Convert(TreeNode root) {
        // 中序遍历建立左孩子的过程中,树结构改变了,比如10的左孩子指向了8
        if (root == null) {
            return null;
        }
        Convert(root.left);
        if (pre == null) {
            // pre为空,遇到双向链表第一个节点,只是记录即可
            head = root;
        } else {
            // 前置节点和当前节点之间建立双向连接
            pre.right = root;
            root.left = pre;
        }
        pre = root;
        Convert(root.right);

        return head;
    }
}

JZ8 二叉树的下一个结点

java
public class Solution {
   public TreeLinkNode GetNext(TreeLinkNode pNode) {
       // 找到pNode的下一个节点,不确定pNode在树中的位置
       // 情况一:有右子树,右子树按照中序第一个元素就是ans
       // 情况二:无右子树,往左上角爬到父节点,然后父节点的下一个节点就是ans
       TreeLinkNode cur = pNode;
       if (cur.right != null) {
           cur = cur.right;
           while (cur.left != null) {
               cur = cur.left;
           }
       } else {
           while (cur.next != null && cur.next.right == cur) {
               cur = cur.next;
           }
           cur = cur.next;
       }

       return cur;
   }
}

JZ26 树的子结构

java
public class Solution {
    // 解法一:递归,(当前根节点开始比较 || 左节点开始比较 || 右节点开始比较)
    // 看题目条件空树不是任意树的子结构
    public boolean HasSubtree(TreeNode root1, TreeNode root2) {
        if (root1 == null || root2 == null) {
            return false;
        }

        return verifyRoot(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
    }

    public boolean verifyRoot(TreeNode root1, TreeNode root2) {
        // root2为空直接true
        // root1空,root2不空false
        // 都不空,比较val
        if (root2 == null) {
            return true;
        } else if (root1 == null) {
            return false;
        } else if (root1.val != root2.val) {
            return false;
        }

        return verifyRoot(root1.left, root2.left) && verifyRoot(root1.right, root2.right);
    }
}

JZ31 栈的压入、弹出序列

java
public class Solution {
    public boolean IsPopOrder (int[] pushV, int[] popV) {
        // 需要一个栈和一个指针,前者模拟入栈,后者在每次入栈后循环出栈
        Deque<Integer> stk = new ArrayDeque<>();
        int i = 0;
        for (int x : pushV) {
            stk.push(x);
            while (!stk.isEmpty() && stk.peek() == popV[i]) {
                stk.pop();
                i++;
            }
        }

        return stk.isEmpty();
    }
}

JZ73 翻转单词序列

java
public class Solution {
    public String ReverseSentence(String str) {
        StringBuilder stringBuilder = new StringBuilder();
        // 从后往前使用stringBuilder接收字符串
        for (int i = str.length() - 1, j = 0; i >= 0; i--) {
            char ch = str.charAt(i);
            if (ch != ' ' && (i + 1 == str.length() || str.charAt(i + 1) == ' ')) {
                j = i;
            }
            if (ch != ' ' && (i == 0 || str.charAt(i - 1) == ' ')) {
                stringBuilder.append(str.substring(i, j + 1) + " ");
            }
        }
        // 如果添加过单词,删除末尾的空格
        if (stringBuilder.length() != 0) {
            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
        }

        return stringBuilder.toString();
    }
}

JZ11 旋转数组的最小数字

java
// 有重复和LeetCode不一样
public class Solution {
    public int minNumberInRotateArray (int[] nums) {
        int l = 0, r = nums.length - 1, mid;
        if (nums[0] < nums[r]) return nums[0];
        while (l < r) {
            mid = l + r >> 1;
            if (nums[mid] > nums[r]) {
                l = mid + 1;
            } else if (nums[mid] < nums[r]) {
                r = mid;
            } else {
                // 牛客这题存在重复值,对于相等情况一个一个试
                r--;
            }
        }
        return nums[l];
    }
}

JZ38 字符串的排列

java
public class Solution {
    private ArrayList<String> ans = new ArrayList<>();
    public ArrayList<String> Permutation (String str) {
        char[] arr = str.toCharArray();
        Arrays.sort(arr);
        // 回溯全排列,flag,同层去重(排序),长度固定,depth
        // 去重是核心,如果当前深度发现前面存在没有使用过的相同字符,表示这个字符在当前深度重复了
        dfs(arr, new char[str.length()], new boolean[str.length()], 0);
        return ans;
    }

    public void dfs(char[] arr, char[] res, boolean[] flag, int depth) {
        if (depth == arr.length) {
            ans.add(new String(res));
            return ;
        }
        for (int i = 0; i < arr.length; i++) {
            // 这里最后应该是!flag,但是第一次提交不取反也是对的,不知道是不是用例问题
            if (flag[i] || (i > 0 && arr[i] == arr[i - 1] && !flag[i - 1])) continue;
            flag[i] = true;
            res[depth] = arr[i];
            dfs(arr, res, flag, depth + 1);
            flag[i] = false;
        }
    }
}

JZ71 跳台阶扩展问题

java
public class Solution {
    public int jumpFloorII (int number) {
        // f(n) = f(n - 1) + ... + f(1) + f(0)
        // f(0) = 1
        // f(1) = f(0) = 1
        // f(2) = f(1) + f(0) = 1 + 1
        // f(3) = f(2) + f(1) + f(0) = 2 + 1 + 1

        return 1 << number - 1;
    }
}

JZ48 最长不含重复字符的子字符串

java
// 与LeetCode相同题目,这里解法稍作改动
public class Solution {
    public int lengthOfLongestSubstring (String s) {
        // 数组代替哈希表,窗口内字符不重复即可
        int ans = 0;
        boolean[] ch = new boolean[128];
        for (int i = 0, j = 0; j < s.length(); j++) {
            // 如果当前字符已经被使用过,则一直收缩左边界
            while (ch[s.charAt(j)]) {
                ch[s.charAt(i++)] = false;
            }

            ch[s.charAt(j)] = true;
            ans = Math.max(ans, j - i + 1);
        }
        return ans;
    }
}

JZ12 矩阵中的路径

java
class Solution {
  public:
    bool dfs(vector<vector<char> >& matrix, string& word, int x, int y, int depth) {
        if (x < 0 || x >= matrix.size() || y < 0 || y >= matrix[0].size() 
                  || depth > word.size() || matrix[x][y] != word[depth - 1]) {
            return false;
        }
        if (depth == word.size()) {
            return true;
        }
        char temp = matrix[x][y];
        matrix[x][y] = '#';

        bool res = dfs(matrix, word, x - 1, y, depth + 1) ||
                   dfs(matrix, word, x + 1, y, depth + 1) ||
                   dfs(matrix, word, x, y - 1, depth + 1) ||
                   dfs(matrix, word, x, y + 1, depth + 1);

        matrix[x][y] = temp;

        return res;
    }

    bool hasPath(vector<vector<char> >& matrix, string word) {
        for (int i = 0; i < matrix.size(); i++) {
            for (int j = 0; j < matrix[0].size(); j++) {
                if (matrix[i][j] == word[0]) {
                    if (dfs(matrix, word, i, j, 1)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }
};

JZ41 数据流中的中位数

java
public class Solution {
    private PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b)->b - a);
    private PriorityQueue<Integer> minHeap = new PriorityQueue<>();

    public void Insert(Integer num) {
        maxHeap.offer(num);                  // 先如左边大顶堆
        minHeap.offer(maxHeap.poll());       // 大顶堆的最大值换到右边小顶堆
        if (maxHeap.size() < minHeap.size()) {
            maxHeap.offer(minHeap.poll());   // 如果大顶堆个数小于小顶堆,再移一个回来
        }
    }
    public Double GetMedian() {
        Double ans = 0.0;
        if (maxHeap.size() > minHeap.size()) {
            ans = (double)maxHeap.peek();
        } else {
            ans = (double)(maxHeap.peek() + minHeap.peek()) / 2;
        }

        return ans;
    }
}

JZ65 不用加减乘除做加法

java
public class Solution {
    public int Add(int num1, int num2) {
        while (num2 != 0) {
            int temp = num1;
            num1 = temp ^ num2;
            num2 = temp & num2;
            num2 <<= 1;
        }
        return num1;
    }
}

JZ 15二进制中1的个数

java
// 剑指offer中有消去最后一个0的方法
public class Solution {
    public int NumberOf1(int n) {
        int cnt = 0;
        while (n != 0) {
            n &= n - 1;
            cnt++;
        }
        return cnt;
    }
}

JZ61 扑克牌顺子

java
// 可以用哈希查看是否重复,这里用bitmap位运算代替哈希
public class Solution {
    public boolean IsContinuous(int[] numbers) {
        int min = 14;
        int max = -1;
        int bitmap = 0;
        for (int num : numbers) {
            if (num == 0) continue;     // 等于0的跳过
            min = Math.min(min, num);   // 更新最大最小
            max = Math.max(max, num);
            if (max - min > 4 || (bitmap & (1 << num)) != 0) {
                return false;
            }
            bitmap ^= (1 << num);
        }
        return true;
    }
}

JZ50 第一个只出现一次的字符

java
public class Solution {
    public int FirstNotRepeatingChar (String str) {
        // 方法一:二次遍历,第一次记录哈希表,第二次查哈希表
        // 方法二:队列+哈希思想,数组代替哈希表,指针ans代替队列头部
        int ans = 0;
        int[] map = new int[128];
        for (int i = 0; i < str.length(); i++) {
            map[str.charAt(i)]++;
            // 每次遍历一个新的字符会更新哈希表,所以可以持续弹出队首
            while (ans < str.length() && map[str.charAt(ans)] > 1) {
                ans++;
            }
        }

        // 如果所有元素出队,证明没有满足条件的字符 -1
        return ans == str.length() ? -1 : ans;
    }
}

JZ56 数组中只出现一次的两个数字

java
public class Solution {
    public int[] FindNumsAppearOnce (int[] nums) {
        // 关键是将nums划分为两组,每组只包含一个结果
        // 找到x, y任意一个不同的二进制位即可
        // 第一遍求 x^y, 找到最低位为1的mask
        // 用mask划分nums

        int x = 0, y = 0;
        int mask = 1, temp = 0;
        for (int num : nums) {
            temp ^= num;
        }
        while ((temp & mask) == 0) {
            mask <<= 1;
        }
        for (int num : nums) {
            if ((num & mask) == 0) {
                x ^= num;
            } else {
                y ^= num;
            }
        }
        if (x < y) {
            return new int[] {x, y};
        } else {
            return new int[] {y, x};
        }
    }
}

JZ14 剪绳子

java
// 解法一:动态规划
public class Solution {
    public int cutRope (int n) {
        int ans, res1, res2;
        int[] dp = new int[n + 1];
        dp[2] = 1;
        // 考虑 n -> [2, 60],只需要下标2赋值即可,下面循环从3开始,因此赋值和循环都不会越界
        // 内层不优化是一个for循环,遍历前面所有结果dp[i] = Math.max(dp[i], j * Math.max(dp[i - j], i - j))
        // 优化后,只剪2或者3,每种情况又分两种情况,max(只剪2段,乘上已知最大)
        for (int i = 3; i <= n; i++) {
            res1 = 2 * Math.max(dp[i - 2], i - 2);
            res2 = 3 * Math.max(dp[i - 3], i - 3);
            dp[i] = Math.max(res1, res2);
        }
        return dp[n];
    }
}


// 解法二:数学公式,n为2,3对应1,2;其他情况尽可能划分出3,余数为1的话拿出一个3凑成4
public class Solution {
    public int cutRope (int n) {
        // 条件2 <= n <= 60;
        if (n < 4) {
            return n - 1;
        }
        int ans;
        int exponent = n / 3;
        int remainder = n % 3;
        if (remainder == 0) {
            ans = (int) Math.pow(3, exponent);             // 注意公式返回double需要类型转换
        } else if (remainder == 1) {
            ans = (int) Math.pow(3, exponent - 1) * 4;
        } else {
            ans = (int) Math.pow(3, exponent) * 2;
        }

        return ans;
    }
}