剑指OFFER

月伴飞鱼 2024-11-13 23:57:51
算法相关
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!

3数组中重复的数字

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。

数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。

请找出数组中任意一个重复的数字。

示例 1:

输入:

[2, 3, 1, 0, 2, 5, 3]

输出:2 或 3

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;
    }
}

4二维数组中的查找

class Solution {
    private boolean flag = false; // 如果flag等于false则没有该证书,如果等于true则有
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        // 从右上角开始,路径只有左和下
        // 所以如果target大于当前值,就往左,如果小于,就往下

        // 数组问题记得判空
        if (matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        int row = 0, column = matrix[0].length - 1;

        while (row < matrix.length && column >= 0) {
            if (matrix[row][column] == target) {
                return true;
            } else if (matrix[row][column] > target) {
                column--;
            } else {
                row++;
            }
        }
        return false;
    }
}

6从尾到头打印链表

/**
 * 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[] res = new int[tmp.size()];
        for(int i = 0; i < res.length; i++) {
            res[i] = tmp.get(i);
        }
        return res;
    }
    void recur(ListNode head) {
        if(head == null) {
            return;
        }
        recur(head.next);
        tmp.add(head.val);
    }
}

时间复杂度 O(N): 遍历链表,递归 N 次。

空间复杂度 O(N): 系统递归需要使用 O(N) 的栈空间。

9用两个栈实现队列

class CQueue {
    //两个栈
    Stack<Integer> input,output;
    public CQueue() {
        input = new Stack<Integer>();
        output = new Stack<Integer>();
    }
    
    public void appendTail(int value) {
        input.push(value);
    }
    public int deleteHead() {
        //转移操作
        if(output.empty()){
            if(input.empty()){
                return -1;
            }
            while(!input.empty()){
                output.push(input.pop());
            }
        }
        return output.pop();
    }
}

15二进制中1的个数

public class Solution {
    public int hammingWeight(int n) {
        int count = 0;
        while(n!=0) {
            n = n&(n-1);
            count++;
        }
        return count;
    }
}

18删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        if (head.val == val) {
            return head.next;
        }
        
        ListNode pre = head, cur = head.next;
        while (cur!=null&&cur.val!=val) {
            pre=cur;
            cur=cur.next;
        }
        if (cur!=null) {
            pre.next=cur.next;
        }
        return head;
    }
}

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

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

示例:

输入:nums = [1,2,3,4]

输出:[1,3,2,4]

class Solution {
    public int[] exchange(int[] nums) {
        int i = 0, j = nums.length - 1, tmp;
        while(i < j) {
            while(i < j && (nums[i] & 1) == 1) {
                i++;
            }
            while(i < j && (nums[j] & 1) == 0) {
                j--;
            }
            tmp = nums[i];
            nums[i] = nums[j];
            nums[j] = tmp;
        }
        return nums;
    }
}

22链表中倒数第K个节点

输入一个链表,输出该链表中倒数第K个结点。

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        if (head == null) {
            return null;
        }
        //1->2->3->4->5
        ListNode pre = head, after = head;
        for (int i = 0; i < k;i++) {
            pre = pre.next;
        }
        while (pre != null) {
            pre = pre.next;
            after = after.next;
        }
        return after;
    }
}

24反转链表

迭代

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while(cur!=null) {
            //记录当前节点的下一个节点
            ListNode tmp = cur.next;
            //然后将当前节点指向pre
            cur.next = pre;
            //pre和cur节点都前进一位
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
}

递归

class Solution {
    public ListNode reverseList(ListNode head) {
        //递归终止条件是当前为空,或者下一个节点为空
        if(head==null || head.next==null) {
            return head;
        }
        //这里的cur就是最后一个节点
        ListNode cur = reverseList(head.next);
        //如果链表是 1->2->3->4->5,那么此时的cur就是5
        //而head是4,head的下一个是5,下下一个是空
        //所以head.next.next 就是5->4
        head.next.next = head;
        //防止链表循环,需要将head.next设置为空
        head.next = null;
        //每层递归函数都返回cur,也就是最后一个节点
        return cur;
    }
}

25合并两个排序的链表

迭代

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null || l2 == null) {
            return l1 == null ? l2 : l1;
        }

        ListNode head = new ListNode(-1);//新增头节点
        ListNode cur = head;//指向已合并部分链表的最后一个节点

        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;
        }
        //不为空的那条链表的剩余部分接在合并链表的末尾
        cur.next = (l1 == null ? l2 : l1);
        return head.next;
    }
}

递归

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        if (l1.val <= l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

26树的子结构

class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if (A == null || B == null) {
            return false;
        }
        //先从根节点判断B是不是A的子结构,如果不是在分别从左右两个子树判断
        //只要有一个为true,就说明B是A的子结构
        return isSub(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
    }

    boolean isSub(TreeNode A, TreeNode B) {
        //这里如果B为空,说明B已经访问完了,确定是A的子结构
        if (B == null) {
            return true;
        }
        //如果B不为空A为空,或者这两个节点值不同,说明B树不是A的子结构,直接返回false
        if (A == null || A.val != B.val) {
            return false;
        }
        //当前节点比较完之后还要继续判断左右子节点
        return isSub(A.left, B.left) && isSub(A.right, B.right);
    }
}

时间复杂度 O(MN) :

  • 其中 M,N 分别为树 A 和 树 B 的节点数量,先序遍历树 A 占用 O(M) ,每次调用 isSub(A, B) 判断占用 O(N) 。

空间复杂度 O(M)

27二叉树的镜像

递归

/**
 * 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 left = root.left;
        TreeNode right = root.right;
        root.left = right;
        root.right = left;
        mirrorTree(root.left);
        mirrorTree(root.right);
        return root;
    }
}

时间复杂度:O(n),其中 n 是二叉树的结点数,每个结点都被访问一次,对于每个结点交换左右子树的时间都是 O(1)。

空间复杂度:O(n),其中 n 是二叉树的结点数。

  • 空间复杂度主要是递归调用的栈空间,取决于二叉树的高度,最坏情况下二叉树的高度是 O(n)。

迭代

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        Queue<TreeNode> queue = new ArrayDeque<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            TreeNode left = node.left, 
          	right = node.right;
            node.left = right;
            node.right = left;
            if (right != null) {
                queue.offer(right);
            }
            if (left != null) {
                queue.offer(left);
            }
        }
        return root;
    }
}

31栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Stack<Integer> stack = new Stack<>();
        int popIndex = 0;
        for (int i = 0; i < pushed.length; i++){
            stack.push(pushed[i]);
            while(!stack.isEmpty()&&stack.peek()==popped[popIndex]) {
                stack.pop();
                popIndex++;
            }
        }
        return stack.isEmpty();
    }
}

32II从上到下打印二叉树II

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root==null) {
            return result;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> temp = new ArrayList<>();
            for (int i = 0;i<size;i++) {
                TreeNode node = queue.poll();
                temp.add(node.val);
                if (node.left!=null) {
                    queue.add(node.left);
                }
                if (node.right!=null) {
                    queue.add(node.right);
                }
            }
            result.add(temp);
        }
        return result;
    }
}

32III从上到下打印二叉树III

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        if(root != null) {
            queue.add(root);
        }
        while(!queue.isEmpty()) {
            List<Integer> tmp = new ArrayList<>();
            for(int i = queue.size(); i > 0; i--) {
                TreeNode node = queue.poll();
                tmp.add(node.val);
                if(node.left != null) {
                    queue.add(node.left);
                }
                if(node.right != null) {
                    queue.add(node.right);
                }
            }
            if((res.size() & 1) == 1) {
                Collections.reverse(tmp);
            }
            res.add(tmp);
        }
        return res;
    }
}

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

请实现一个函数来判断整数数组 postorder 是否为二叉搜索树的后序遍历结果。

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        return helper(postorder, 0, postorder.length - 1);
    }

    boolean helper(int[] postorder, int left, int right) {
        if (left >= right) {
            return true;
        }

        //因为数组中最后一个值postorder[right]是根节点,这里从左往右找出第一个比
        //根节点大的值,他后面的都是根节点的右子节点(包含当前值,不包含最后一个值
        //因为最后一个是根节点),他前面的都是根节点的左子节点
        int mid = left;
        int root = postorder[right];
        while (postorder[mid] < root) {
            mid++;
        }
        int temp = mid;
        //因为postorder[mid]前面的值都是比根节点root小的
        //我们还需要确定postorder[mid]后面的值都要比根节点root大
        //如果后面有比根节点小的直接返回false
        while (temp < right) {
            if (postorder[temp++] < root) {
                return false;
            }
        }
        //然后对左右子节点进行递归调用
        return helper(postorder, left, mid - 1) && helper(postorder, mid, right - 1);
    }
}

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

/**
 * 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> path = new LinkedList<>(); 
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        recur(root, sum);
        return res;
    }
    void recur(TreeNode root, int tar) {
        if(root == null) {
            return;
        }
        path.add(root.val);
        tar -= root.val;
        if(tar == 0 && root.left == null && root.right == null) {
            res.add(new LinkedList(path));
        }
        recur(root.left, tar);
        recur(root.right, tar);
        path.removeLast();
    }
}

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

class Solution {
    public int majorityElement(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int result = nums[0], count = 1;
        for (int num : nums) {
            if (num == result) {
                count++;
            } else {
                count--;
            }
            if (count == 0) {
                count = 1;
                result = num;
            }
        }
        return result;
    }
}

42连续子数组的最大和

class Solution {
    public int maxSubArray(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int result = nums[0];
        int current = nums[0];
        for (int i = 1; i<nums.length;i++) {
            current = Math.max(current + nums[i], nums[i]);
            result = Math.max(result, current);
        }
        return result;
    }
}

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

使用两个指针 node1,node2 分别指向两个链表 headA,headB 的头结点,然后同时分别逐结点遍历,当 node1 到达链表 headA 的末尾时,重新定位到链表 headB 的头结点。

当 node2 到达链表 headB 的末尾时,重新定位到链表 headA 的头结点。

这样,当它们相遇时,所指向的结点就是第一个公共结点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }

        ListNode node1 = headA, node2 = headB;
        while (node1 != node2) {
            //当node1为空,则从headB遍历
            node1 = (node1 == null ? headB : node1.next);
            //当node2为空,则从headA遍历
            node2 = (node2 == null ? headA : node2.next);
        }
        return node1;
    }
}

53缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0到n-1之内。

在范围0到n-1的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

由于是排序数组,考虑使用二分查找算法:

  • 当中间数字等于其下标时,在后半部分查找

  • 当中间数字不等于其下标时

    • 如果中间数字的前一个数字也不等于其下标,则在前半部分查找
    • 如果中间数字的前一个数字等于其下标,则说明中间数字的下标即为我们所要找的数字
class Solution {
    public int missingNumber(int[] nums) {
        int i = 0, j = nums.length - 1;
        while(i <= j) {
            int m = (i + j) / 2;
            if(nums[m] == m) {
                i = m + 1;
            } else {
                j = m - 1;
            }
        }
        return i;
    }
}

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 res, k;
    public int kthLargest(TreeNode root, int k) {
        this.k = k;
        dfs(root);
        return res;
    }
    void dfs(TreeNode root) {
        if(root == null) {
            return;
        }
        dfs(root.right);
        if(k == 0) {
            return;
        }
        if(--k == 0) {
            res = root.val;
        }
        dfs(root.left);
    }
}

55二叉树的深度

DFS

class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftLen = maxDepth(root.left);
        int rightLen = maxDepth(root.right);
        return Math.max(leftLen, rightLen) + 1;
    }
}

时间复杂度 O(N) : N 为树的节点数量,计算树的深度需要遍历所有节点。

BFS

class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) {
            return 0;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int res = 0;
        while(!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0;i<size;i++) {
                TreeNode node = queue.poll();
                if(node.left != null) {
                    queue.offer(node.left);
                }
                if(node.right != null) {
                    queue.offer(node.right);
                }
            }
            res++;
        }
        return res;
    }
}

63股票的最大利润

class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0) {
            return 0;
        }
        int minPrice = Integer.MAX_VALUE;
        int profit = 0;
        for (int price : prices) {
            minPrice = Math.min(minPrice, price);
            profit = Math.max(profit, price - minPrice);
        }
        return profit;
    }
}

65不用加减乘除做加法

class Solution {
    public int add(int a, int b) {
        while(b != 0) { // 当进位为 0 时跳出
            int c = (a & b) << 1;  // c = 进位
            a ^= b; // a = 非进位和
            b = c; // b = 进位
        }
        return a;
    }
}

68I二叉搜索树的最近公共祖先

从根结点开始搜索,如果根结点的值大于节点p 和节点 q,证明这两个节点都在当前节点的左子树上,于是可以继续沿着左子树向下搜索

如果根结点的值小于节点p 和节点 q,证明这两个节点都在当前节点的右子树上,可以继续沿着右子树向下搜索

如果当前节点大于一个节点且小于另一个节点,则证明两个节点分别处于当前节点的左右两侧, 则该节点就是这两个节点的公共祖先

若都不满足则返回空

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || p == null || q == null) {
            return null;
        }
        if (root.val < p.val && root.val < q.val && root.right != null) {
            return lowestCommonAncestor(root.right, p, q);
        }
        if (root.val > p.val && root.val > q.val && root.left != null) {
            return lowestCommonAncestor(root.left, p, q);
        }
        return root;
    }
}

68II二叉树的最近公共祖先

左右子树分别进行递归,即查找左右子树上是否有p结点或者q结点,就一共有4种情况:

第一种情况:左子树和右子树均找没有p结点或者q结点

第二种情况:左子树上能找到,但是右子树上找不到,此时就应当直接返回左子树的查找结果

第三种情况:右子树上能找到,但是左子树上找不到,此时就应当直接返回右子树的查找结果

第四种情况:左右子树上均能找到,说明此时的p结点和q结点分居root结点两侧,此时就应当直接返回root结点了

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) {
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left == null) {
            return right;
        }
        if (right == null) {
            return left;
        }
        return root;
    }
}
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!