Leetcode面试经典150题

数组字符串

合并两个有序数组

思路

类似于归并排序,对两个有序数组进行合并即可,但是空间复杂度是O(n+m);

代码

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int[] ans = new int[n + m];
        int i = 0, j = 0;
        int cnt = 0;
        while (i < m && j < n) {
            if (nums1[i] < nums2[j]) {
                ans[cnt++] = nums1[i++];
            } else {
                ans[cnt++] = nums2[j++];
            }
        }
        while(i < m) ans[cnt++] = nums1[i++];
        while(j < n) ans[cnt++] = nums2[j++];
        for (int k = 0; k < cnt; ++k) {
            nums1[k] = ans[k];
        }
    }
}

优化:有一点小优化吧,可以从后往前合并装入nums1的后面,这样不会影响nums1的元素,同样也节省了n+m的空间(本题的数据量小,所以看不出)。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m - 1;
        int j = n - 1;
        int cnt = n + m - 1;
        while(i >= 0 && j >= 0) {
            if (nums1[i] < nums2[j]) {
                nums1[cnt--] = nums2[j--];
            } else {
                nums1[cnt--] = nums1[i--];
            }
        }
        while(i >= 0) nums1[cnt--] = nums1[i--];
        while(j >= 0) nums1[cnt--] = nums2[j--];
    }
}

移除元素

思路

原地移除,用一个类似于指针的变量ind,从0开始遍历,如果与val相同则一直往后走,如果不同则压入到ind这个位置

代码

class Solution {
    public int removeElement(int[] nums, int val) {
        int ind = 0;
        int i = 0;
        while (i < nums.length) {
            int j = i;
            while (j < nums.length && nums[j] == val) j++;
            if (j < nums.length) nums[ind++] = nums[j];
            i = j + 1;
        }
        return ind;
    }
}

删除有序数组中的重复项

思路

与上一个题相似,过滤掉相同的数字即可。

代码

class Solution {
    public int removeDuplicates(int[] nums) {
         int cnt = 1;
         int n = nums.length;
         for (int i = 0; i < n;) {
            int j = i + 1;
            while(j < n && nums[j] == nums[i]) ++j;
            if (j >= n) break;
            nums[cnt++] = nums[j];
            i = j;
         }
         return cnt;
    }
}

双指针

验证回文串

思路

用头指针和尾指针,每次比较的时候必须满足两个指针指向的是数字或字符,其他的符号都跳过。

代码

class Solution {
    public boolean isPalindrome(String s) {
        s = s.toLowerCase();
        int len = s.length();
        int i = 0, j = len - 1;
        char[] ch = s.toCharArray();
        while(i < j) {
            while(i < len && !check(ch[i])) ++i;
            while(j >= 0 && !check(ch[j])) --j;//前面两句是过滤非字符或数字
            if (i > j) {
                break;
            }
            if (ch[i] >= 'a') {
                ch[i] -= 32;
            }
            if (ch[j] >= 'a') {
                ch[j] -= 32;
            }// 如果是字符,则统一转为小写
            if (ch[i] != ch[j]) {
                return false;
            }
            ++i;
            --j;
        }
        return true;
    }
    private boolean check(char ch) {
        if (ch <= 'z' && ch >= 'a') {
            return true;
        }
        if (ch <= 'Z' && ch >= 'z') {
            return true;
        }
        if (ch <= '9' && ch >= '0') {
            return true;
        }
        return false;
    }
}

滑动窗口

长度最小的子数组

思路

由滑动窗口思想:设两个指针,尾指针一直走,当满足某个条件时,头指针也跟着走。
条件:当子数组和大于target时,不断缩小窗口,找到最小的窗口。

代码

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int ans = nums.length;
        int ind = 0;
        int sum = 0;
        for (int i = 0; i < nums.length; ++i) {
            sum += nums[i];
            while (sum >= target) {
                sum -= nums[ind];
                ans = Math.min(ans, i - ind + 1);
                ind++;
            }
        }
        if (ind == 0) { // sum>=target没有执行,不存在子数组
            return 0;
        } 
        return ans;
    }
}

矩阵

有效的数独

思路

创建三个set数组,分别是对存“行”,“列”,“区域”的数字,如果对应的set中有值,那么就不是有效的。否则就添加
这里最主要的是怎样判断是否为同一个区域。
可以先对9x9的表格通过i/3,j/3划分为9个3x3区域然后每个区域的值都是
(0, 0), (0, 1), (0, 2)
(1, 0), (1, 1)…
再通过将二维数组变为一维数组的计算公式i * row + j。就是9个区域。

代码

class Solution {
    public boolean isValidSudoku(char[][] board) {
        Set<Character>[] rows = new HashSet[9];
        Set<Character>[] cols = new HashSet[9];
        Set<Character>[] area = new HashSet[9];
        for (int i = 0; i < 9; ++i) {
            rows[i] = new HashSet<>();
            cols[i] = new HashSet<>();
            area[i] = new HashSet<>();
        }
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] > '9' || board[i][j] < '0') continue;
                if (rows[i].contains(board[i][j])) {
                    return false;
                } else {
                    rows[i].add(board[i][j]);
                }

                if (cols[j].contains(board[i][j])) {
                    return false;
                } else {
                    cols[j].add(board[i][j]);
                }
                int t= i / 3 * 3 + j / 3;
                //System.out.println(i + " " + j + " " + t);
                if (area[t].contains(board[i][j])) {
                    return false;
                } else {
                    area[t].add(board[i][j]);
                }
            }
        }
        return true;
    }
}

哈希表

赎金信

思路

比较r和m的26个字符个数,如果r的某个字符个数大于m的某个字符个数,则r不能由m的字符组成。

代码

class Solution {
    public boolean canConstruct(String r, String m) {
        int[] rCnt = new int[26];
        int[] mCnt = new int[26];
        for (int i = 0; i < r.length(); ++i) {
            rCnt[r.charAt(i) - 'a']++;
        }
        for (int i = 0; i < m.length(); ++i) {
            mCnt[m.charAt(i) - 'a']++;
        }
        for (int i = 0; i < 26; ++i) {
            if (rCnt[i] > mCnt[i]) {
                return false;
            }
        }
        return true;
    }
}

区间

汇总区间

思路

简单模拟

代码

class Solution {
    public List<String> summaryRanges(int[] nums) {
        List<String> ans = new ArrayList<>();
        int n = nums.length;
        for (int i = 0; i < n;) {
            int j = i;
            while(j < n -1 && nums[j] + 1 == nums[j + 1]) j++;
            if (j == i) {
                ans.add("" + nums[i]);
                ++i;
            } else {
                ans.add(nums[i] + "->" + nums[j]);
                i = j + 1;
            }
        }
        return ans;
    }
}

有效括号

思路

如果能够匹配则删除栈顶元素,如果不能匹配就进栈,最后判断是否为空。

代码

class Solution {
    static final Map<Character, Character> map = new HashMap<>() {{
        put('(',')');
        put('{','}');
        put('[',']');
    }};
    public boolean isValid(String s) {
        char[] ch = s.toCharArray();
        Stack<Character> stack = new Stack<>();
        
        for (char c : ch) {
            if (stack.isEmpty()) {
                stack.push(c);
                continue;
            }
            Character peek = stack.peek();
            if (map.containsKey(peek) && map.get(peek) == c) {
                stack.pop();
            } else {
                stack.push(c);
            }
        }
        return stack.isEmpty();
    }
}

链表

环形链表

思路

链表长度最长1e4,头结点一直往后走,如果次数超过链表长度,必定有环(投机取巧了)

代码

public class Solution {
    public boolean hasCycle(ListNode head) {
        int cnt = 0;
        while(head != null) {
            head = head.next;
            cnt++;
            if (cnt > 10005) {
                return true;
            }
        }
        return false;
    }
}

正解:快慢指针,根据Floyd判圈法,又称为龟兔赛跑算法,乌龟每次走一格,兔子每次走两格,如果有环,兔子提前进入环并且一直在环内运动,而当乌龟进入环时,兔子一定会追到乌龟。

public class Solution {
    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;
    }
}

二叉树

二叉树的最大深度

思路

模拟走一遍二叉树同时记录层数。

代码

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

二叉树的遍历

二叉树的右视图

思路

二叉树的层序遍历,取每层的最后一个节点即可。

代码

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        int cnt = 0, lastVal = 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()) {
            cnt = queue.size();// 每层有多少个节点
            while(cnt-- > 0) {
                TreeNode curNode = queue.poll();
                if (curNode.left != null) queue.add(curNode.left);
                if (curNode.right != null) queue.add(curNode.right);
                lastVal = curNode.val;
            }
            ans.add(lastVal);//每层的最后一个节点
        }
        return ans;
    }
    
}

二叉搜索树

二叉搜索树的最小绝对差

思路

二叉搜索树的中序遍历是升序,而升序的相邻节点可以得到一个最小值,即答案所求。

代码

class Solution {
    int ans = Integer.MAX_VALUE;
    int pre = -1; // 记录中序遍历的前一个节点,初始化为-1,是还没找到中序遍历的第一个节点
    public int getMinimumDifference(TreeNode root) {
        dfs(root);
        return ans;
    }
    private void dfs(TreeNode root) {
        if (root == null) {
            return;
        }
        dfs(root.left);
        if (pre == -1) {
            pre = root.val;
        } else {
            ans = Math.min(ans, root.val - pre);
            pre = root.val;
        }
        dfs(root.right);
    }
}

岛屿数量

思路

dfs没走过的为1的格子

代码

class Solution {
    int N = 305;
    static int[][] dir = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
    boolean[][] vis = new boolean[N][N];
    int n, m, ans;
    public int numIslands(char[][] grid) {
        m = grid.length;
        n = grid[0].length;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (vis[i][j] || grid[i][j] == '0') continue;
                dfs(grid, i, j);
                //System.out.println(i + "<->" + j);
                ans++;
            }
        }
        return ans;
    }
    private void dfs(char[][] g, int x, int y) {
        vis[x][y] = true;
        for (int i = 0; i < 4; ++i) {
            int curX = x + dir[i][0];
            int curY = y + dir[i][1];
            if (curX < 0 || curY < 0 || curX >= m || curY >= n || g[curX][curY] == '0' || vis[curX][curY]) continue;
            dfs(g, curX, curY);
        }
    }
}

图广度优先搜索

蛇梯棋

思路

题实在没读懂,参考解析
利用广度优先搜索,先走格子,如果遇到梯子或蛇就直接传送(传送不花时间)。

代码

class Solution {
    int n;
    int[] g;
    public int snakesAndLadders(int[][] board) {
        n = board.length;
        boolean isR = true;
        g = new int[n * n + 1];
        int ind = 0;
        for (int i = n - 1; i >= 0; --i) {
            for (int j = (isR ? 0 : n - 1); isR ? j < n : j >= 0; j += isR ? 1 : -1) { // 经典的神龙摆尾
                g[++ind] = board[i][j];
            }
            isR = !isR;
        }
        int ans = bfs();
        return ans;
    }
    private int bfs()  {
        Queue<Integer> queue = new LinkedList<>(); // 当前到的点
        Map<Integer, Integer> m = new HashMap<>(); // 用于存当前点和步数
        m.put(1, 0);
        queue.add(1);
        while(!queue.isEmpty()) {
            int cur = queue.poll();
            int step = m.get(cur);
            if (cur == n * n) return step;
            for (int i = 1; i <= 6; ++i) {
                int nxt = cur + i;
                if (nxt > n * n) continue;
                if (g[nxt] != -1) nxt = g[nxt]; //直接传送
                if (m.containsKey(nxt)) continue; // 已经走过
                m.put(nxt, step + 1);
                queue.add(nxt);
            }
        }
        return -1;
        
    }
}

字典树

实现 Trie (前缀树)

思路

字典树模版题

代码

class Trie {
    static final int N = (int) 1e5 + 9;
    static int[] cnt = new int[N];
    static int[][] tree = new int[N][26];
    static int ind = 0;
    public Trie() {
        for (int i = ind; i >= 0; --i) {
            Arrays.fill(tree[i], 0);
        }
        Arrays.fill(cnt, 0);
        ind = 0;
    }
    
    public void insert(String word) {
        int p = 0;
        for (int i = 0; i < word.length(); ++i) {
            int u = word.charAt(i) - 'a';
            if (tree[p][u] == 0) tree[p][u] = ++ind;
            p = tree[p][u];
        }
        cnt[p]++;
    }
    
    public boolean search(String word) {
        int p = 0;
        for (int i = 0; i < word.length(); ++i) {
            int u = word.charAt(i) - 'a';
            if (tree[p][u] == 0) return false;
            p = tree[p][u];
        }
        return cnt[p] != 0;
    }
    
    public boolean startsWith(String prefix) {
        int p = 0;
        for (int i = 0; i < prefix.length(); ++i) {
            int u = prefix.charAt(i) - 'a';
            if (tree[p][u] == 0) return false;
            p = tree[p][u];
        }
        return true;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */

回溯

电话号码的字母组合

思路

将每个键对应的字母存到String里,然后dfs变量每一个键,遍历到的键存到一个数组里,指导存了digits.size()个。

代码

class Solution {
    private static final String[] MAPPING = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

    private final List<String> ans = new ArrayList<>();
    private char[] digits, path;
    public List<String> letterCombinations(String digits) {
        int n = digits.length();
        if (n == 0) return List.of();
        this.digits = digits.toCharArray();
        path = new char[n];
        dfs(0);
        return ans;
    }
    private void dfs(int i) {
        if (i ==digits.length) { // 如果已经按完
            ans.add(new String(path));
            return;
        }
        for (char c : MAPPING[digits[i] - '0'].toCharArray()) { // 枚举按每一个键
            path[i] = c; // 考虑先后顺序,先出现的和后出现的做匹配
            dfs(i + 1);
        }
    }
}

分治

将有序数组转换为二叉搜索树

思路

以每个区间的中点作为根节点进行建树,这是一个分治的过程。

代码

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return dfs(nums, 0, nums.length - 1);
    }
    private TreeNode dfs(int[] nums, int L, int R) {
        if (L > R) {
            return null;
        }
        int mid = (L + R) >> 1;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = dfs(nums, L, mid - 1);
        root.right = dfs(nums, mid + 1, R);
        return root;
    }
}

Kadane 算法

最大子数组和

思路

思维题,用一个sum变量记录当前子数组和的最大值,如果小于0,则不要这段数组,因为任何数加一个小于0的数都会变小。边加边取最大值即可。

代码

class Solution {
    public int maxSubArray(int[] nums) {
        int ans = Integer.MIN_VALUE;
        int sum =0;
        for (int num : nums) {
            if (sum < 0) {
                sum = 0;
            }
            sum += num;
            ans = Math.max(ans, sum);
        }
        return ans;
    }
}

二分查找

搜索插入位置

思路

二分查找模版题

代码

class Solution {
    public int searchInsert(int[] nums, int target) {
        int l = 0, r = nums.length - 1;
        while (l <= r) {
            int mid = (l +r) >> 1;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        return l;
    }
}

数组中的第K个最大元素

思路

用优先队列来做,因为优先队列底层就是堆实现的(默认小根堆),然后开一个k+1大小的小根堆,每次维持堆里的元素只有k个,最后输出堆顶,也就是队首即可。

代码

class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> queue = new PriorityQueue<>(k + 1);
        for (int num : nums) {
            queue.offer(num);
            if (queue.size() == k + 1) {
                queue.poll();
            }
        }
        return queue.peek();
    }
}
  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空皓月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值