刷穿力扣(31~60)

news2024/11/18 4:45:12

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验

31. 下一个排列


  • 排列
  • 原理就是 C++ 中的 next_permutation 函数,生成指定序列的下一个全排列
  • 从给定序列的最右端开始,找到第一个满足 nums[i] < nums[i + 1] 的元素 nums[i]
  • 若找不到这样的元素 nums[i]:说明当前序列是最后一个排列,函数将序列重排为第一个排列,并返回 false
  • 若找到了元素 nums[i]:继续从最右端开始,找到第一个满足 nums[i] < nums[j] 的元素 nums[j]
  • 交换 nums[i]nums[j],然后从 i + 1 开始将序列反转,使得它们按照升序排列。
class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;
        int i = n - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) i --;
        if (i >= 0) {
            int j = n - 1;
            while (nums[j] <= nums[i]) j --;
            swap(nums, i, j);
        }
        reverse(nums, i + 1, n - 1);
    }

    private void swap(int[] nums, int i, int j) {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }

    private void reverse(int[] nums, int l, int r) {
        while (l < r) {
            this.swap(nums, l, r);
            l ++; r --;
        }
    }
    
}

32. 最长有效括号


  • Stack<Integer> 存储所有 ( 的下标
  • 当遇到右括号时,检查栈是否为空:
    • 若栈为空,则说明当前右括号没有匹配的左括号,将当前右括号的位置作为新的起始位置。
    • 若不为空,则弹出栈顶元素,并更新当前有效括号的长度(当前右括号的位置减去栈顶元素的值)
  • 遍历完字符串后,得到最长有效括号的长度
class Solution {
    public int longestValidParentheses(String s) {
        Stack<Integer> stack = new Stack<>();
        int ans = 0;
        int start = -1; // 有效括号序列的起始位置

        for (int i = 0; i < s.length(); i++) {
            // 将左括号的位置压入栈中
            if (s.charAt(i) == '(') stack.push(i); 
            else {
                if (stack.isEmpty()) {
                    start = i; // 当前右括号无法匹配,更新起始位置
                } 
                else {
                    stack.pop();
                    // 栈为空,表示当前右括号匹配到有效括号序列的末尾
                    if (stack.isEmpty()) ans = Math.max(ans, i - start);
                    else ans = Math.max(ans, i - stack.peek());  // 栈不为空,计算当前有效括号序列的长度
                }
            }
        }

        return ans;
    }
}

优化

  • 模拟
  • 设当前的括号出现的次数 res,第一次扫代表 (,起始位置 l = 0,第二次扫代表 ),起始位置 r = n - 1
  • 先从左往右扫一遍,若为 (res ++
  • 当遇到 ) 时:
    • res == 0,则更新最长有效括号的长度 ans,并更新起始位置 l
    • 否则:说明匹配,res --,若此时 res == 0,则匹配了全部左括号,更新 ans
  • 第一遍扫完后,判断是否还有未匹配的左括号。如果没有或者最长有效括号的长度 ans 已经大于等于剩余未匹配左括号的长度(n - l),则直接返回 ans
  • 如果还有未匹配的左括号,说明可能出现以右括号结尾的有效括号序列。再从右往左扫第二遍,步骤是一样的。
  • 最终返回最长有效括号的长度 ans
class Solution {
    public int longestValidParentheses(String s) {
        int res = 0;
        int n = s.length();
        int ans = 0, l = 0;
        // 从左扫第一遍
        for (int i = 0; i < n; i ++) {
            if (s.charAt(i) == '(') {
                res ++; continue;
            }
            // 遇到右括号,且当前没有匹配的左括号
            if (res == 0) {
                ans = Math.max(ans, i - l);  // 更新最长有效括号的长度
                l = i + 1;  // 更新起始位置
            } 
            else {
                res --;  // 遇到右括号,且有匹配的左括号,res --
                if (res == 0) ans = Math.max(ans, i - l + 1);
            }
        }
         // 如果遍历完字符串后,仍有未匹配的左括号
        if (res == 0 || ans >= n - l) return ans;
        
        int r = n - 1; res = 0;
        // 从右扫第二遍
        for (int i = n - 1; i > l; i --) {
            if (s.charAt(i) == ')') {
                res ++; continue;
            }
            if (res == 0) {
                ans = Math.max(ans, r - i);
                r = i - 1;
            } 
            else 
                res --;
                if (res == 0) ans = Math.max(ans, r - i + 1);
            }
        }
        
        return ans;
    }
}

33. 搜索旋转排序数组


  • 二分
  • 显然,在旋转点的左侧满足 nums[i] >= nums[0],而右侧 nums[i] < nums[0]
  • 根据上述的单调性我们第一次二分找到旋转点的位置
  • 然后判断 target 在旋转点的左半部分还是右半部分
  • 显然,当 target >= nums[0] 时在左半部分
  • 然后继续二分,更新 l, r 为新的目标区间继续查找
class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        if (n == 1) return nums[0] == target ? 0 : -1;
        int l = 0, r = n - 1;
        // 寻找旋转点,查找第一个不满足 >= nums[0] 的元素下标
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (nums[mid] == target) return mid;
            if (nums[mid] >= nums[0]) l = mid + 1;
            else r = mid - 1; 
        }
        if (l < n && nums[l] == target) return l;
       	// 判断 target 在左半区间还是右半区间
        if (target >= nums[0]) {
            r = l - 1; l = 0;  // 更新边界为左半区间
        }
        else {
            l ++; r = n - 1;   // 更新边界为右半区间
        }
        // 在剩下半个区间查找 == target 的元素下标
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (nums[mid] == target) return mid;
            if (nums[mid] < target) l = mid + 1;
            else r = mid - 1;
        }
        return l < n && nums[l] == target ? l : -1;
    }
}

34. 在排序数组中查找元素的第一个和最后一个位置


  • 二分
  • 数组已从小到大排序,满足单调性
  • 第一遍二分寻找第一个小于 target 的数,期间更新 nums[mid] == target 的下标,即为第一个位置
  • 第二遍二分寻找第一个大于 target 的数,期间更新 nums[mid] == target 的下标,即为第二个位置
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int n = nums.length;
        int l = 0, r = n - 1;
        int res = -1;
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (nums[mid] == target) res = mid;
            if (nums[mid] < target) l = mid + 1;
            else r = mid - 1;
        }
        if (res == -1) return new int[]{-1, -1};
        l = res; r = n - 1;  // l 从 res 开始或 0 开始都行
        int cnt = -1;
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (nums[mid] == target) cnt = mid;
            if (nums[mid] > target) r = mid - 1;
            else l = mid + 1;
        }
        return new int[]{res, cnt};
    }
}

35. 搜索插入位置


  • 二分
  • 没啥好说的板子题
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;
            if (nums[mid] < target) l = mid + 1;
            else r = mid - 1;
        }
        return l;
    }
}

36. 有效的数独


  • 模拟
  • 只需要验证已有的数是否符号规则就行,因此我们可以用区区十几个循环嵌套来解决
  • 首先将数独按照 3 × 3 3 \times 3 3×3 分为 9 9 9 组,然后用 boolean[][] 维护每组格子的状态
  • 具体滴,对于格子 board[i][j],所在的组编号 u 映射为 u = i / 3 * 3 + j / 3
  • boolean[][] vis[u][num] 表示第 u 组数字 num 是否已经存在
  • 那么除此之外,还需要判断行和列的格子的状态即可
class Solution {
    public boolean isValidSudoku(char[][] board) {
        boolean[][] vis = new boolean[10][10];  // 记录小方块的填数状态
        boolean[][] row = new boolean[10][10];  // 记录每一行的填数状态
        boolean[][] col = new boolean[10][10];  // 记录每一列的填数状态

        for (int i = 0; i < 9; i ++) {
            for (int j = 0; j < 9; j ++) {
                char t = board[i][j];
                if (t == '.') continue;  // '.' 无所谓,直接跳过
                int num = t - '0';
                // 组编号映射
                int u = i / 3 * 3 + j / 3;
                // 只要有一个已经存放过都不成立
                if (vis[u][num] || row[i][num] || col[j][num]) return false;
                vis[u][num] = row[i][num] = col[j][num] = true;
            }
        }
        return true;
    }
}

37. 解数独


  • DFS
  • 对于 board[i][j] 1 ∼ 9 1 \sim 9 19 暴力搜索每一个可能的值
  • 难点仍是在于判断是否合法,思路同上一题
class Solution {
    public void solveSudoku(char[][] board) {
        boolean[][] vis = new boolean[10][10];  // 记录每个数字在每一行的填数状态
        boolean[][] row = new boolean[10][10];  // 记录每个数字在每一列的填数状态
        boolean[][] col = new boolean[10][10];  // 记录每个数字在每个小方块的填数状态
        init(board, vis, row, col);  // 初始化状态数组
        dfs(board, 0, 0, vis, row, col);
    }

    private boolean dfs(char[][] board, int i, int j, boolean[][] vis,
                    boolean[][] row, boolean[][] col) {
        if (i == 9) return true;  // 数独已经填满,返回 true
        if (j == 9) return dfs(board, i + 1, 0, vis, row, col);  // 当前行已经填完,转到下一行
        if (board[i][j] != '.') return dfs(board, i, j + 1, vis, row, col);  // 当前位置已经有数字,转到下一个位置
		// 从 1 开始枚举可能的值
        for (int num = 1; num <= 9; num ++) {
            int idx = i / 3 * 3 + j / 3;  // 小方块的索引
            if (!vis[idx][num] && !row[i][num] && !col[j][num]) {
                vis[idx][num] = row[i][num] = col[j][num] = true;
                board[i][j] = (char)(num + '0');  // 填入值
                if (dfs(board, i, j + 1, vis, row, col)) return true;  // 递归填下一个位置
                // 恢复现场
                vis[idx][num] = row[i][num] = col[j][num] = false;     
                board[i][j] = '.';
            }
        }
        return false;  // 无法填入任何数字,返回false
    }

    private void init(char[][] board, boolean[][] vis,
                    boolean[][] row, boolean[][] col) {
        for (int i = 0; i < 9; i ++) {
            for (int j = 0; j < 9; j ++) {
                if (board[i][j] != '.') {
                    int num = board[i][j] - '0';
                    int idx = i / 3 * 3 + j / 3;
                    vis[idx][num] = row[i][num] = col[j][num] = true;
                }
            }
        }
    }
}

38. 外观数列


  • DFS
  • 递归的每一层,对上一层生成的 s 进行遍历
  • char flag 记录当前的字符,cnt 记录当前字符的数量
  • StringBuilder sb 来接收对 s 的描述并且继续搜索
class Solution {
    public String countAndSay(int n) {
        return dfs(n, 1, "1");
    }

    private String dfs(int n, int u, String s) {
        if (u == n) return s;
        StringBuilder sb = new StringBuilder();
        int cnt = 0;
        char flag = s.charAt(0);
        for (int i = 0; i < s.length(); i ++) {
            if (s.charAt(i) == flag) cnt ++;
            else {
                // 当前字符和 flag 不同,说明发生改变
                sb.append(cnt); sb.append(flag);  // 将 flag 字符及其数量 cnt 描述加入 sb
                flag = s.charAt(i); cnt = 1;      // 更新 flag 和 cnt
            }
        }
        // 添加最后一个字符的计数
        sb.append(cnt); sb.append(flag);
        return dfs(n, u + 1, sb.toString());
    }
}

39. 组合总和


  • DFS
  • 首先对 candidates 从小到大排序
  • 递归的每一层视作对 candidates[i] 的选择
  • 由于数组是从小到大单调的,当 cnt + candidates[i] > target 即可不用在向后遍历
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        List<List<Integer>> ans = new ArrayList<>();  // 存储答案
        List<Integer> res = new ArrayList<>();        // 存储递归每一层的结果
        dfs(candidates, target, 0, 0, res, ans);
        return ans;
    }

    private void dfs(int[] candidates, int target, int u, int cnt, 
        List<Integer> res, List<List<Integer>> ans) {
        if (cnt == target) {  // 找到答案
            ans.add(new ArrayList<>(res));  // 将当前结果加入答案
            return ;
        }
        // 从 u 开始选择(因为可以重复)
        for (int i = u; i < candidates.length; i ++) {
            if (cnt + candidates[i] <= target) {  // 满足条件才能递归到下一层
                res.add(candidates[i]);
                dfs(candidates, target, i, cnt + candidates[i], res, ans);
                res.remove(res.size() - 1);  // 恢复现场
            } 
            else break;
        }
    }
}

40. 组合总和 II


  • DFS
  • 和上一题思路一样,不同的是不能包含重复选择
  • 有两种方式:
    • 添加 HashSet<Integer> vis 标记使用过的数
    • 在选择时加一层判断 candidates[i] == candidates[i - 1] 时跳过
class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        List<List<Integer>> ans = new ArrayList<>();  // 存储答案
        List<Integer> res = new ArrayList<>();        // 存储递归每一层的结果
        dfs(candidates, target, 0, 0, res, ans);
        return ans;
    }

    private void dfs(int[] candidates, int target, int u, int cnt, 
        List<Integer> res, List<List<Integer>> ans) {
        if (cnt == target) {  // 找到答案
            ans.add(new ArrayList<>(res));  // 将当前结果加入答案
            return ;
        }
        for (int i = u; i < candidates.length; i ++) {
            if (i > u && candidates[i] == candidates[i - 1]) continue;
            if (cnt + candidates[i] <= target) {  // 满足条件才能递归到下一层
                res.add(candidates[i]);
                dfs(candidates, target, i + 1, cnt + candidates[i], res, ans);
                res.remove(res.size() - 1);  // 恢复现场
            } 
            else break;
        }
    }
}

<<<<<<< HEAD

41. 缺失的第一个正数


  • 模拟
  • 将数组本身视作哈希表
  • 预处理 nums[i] <= 0 || nums[i] > n 的值为 n + 1,表示不存在
  • 将出现的正数对应的位置标记为负数,通过改变正数对应位置上的符号来标记该位置所对应的数值已经出现过
  • 最后,遍历找到第一个为正数的位置,即为缺失的最小正整数。
class Solution {
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        // 第一次遍历,将所有负数和大于 n 的数置为 n + 1
        for (int i = 0; i < n; i ++) {
            if (nums[i] <= 0 || nums[i] > n) nums[i] = n + 1;
        }

        // 第二次遍历,将出现的正数对应的位置标记为负数
        for (int i = 0; i < n; i ++) {
            int num = Math.abs(nums[i]);
            if (num <= n) nums[num - 1] = -Math.abs(nums[num - 1]);
        }

        // 第三次遍历,找到第一个正数的位置,即为缺失的最小正整数
        for (int i = 0; i < n; i ++) {
            if (nums[i] > 0) return i + 1;
        }

        // 如果数组中的所有数都出现了,返回 n + 1
        return n + 1;
    }
}

42. 接雨水


  • 单调栈
  • 维护 height[i] 之前出现过的最高的挡板
  • 对于 height[i] < height[st.peek()] 说明后面可能存在更高的挡板
  • 否则应当结算之前所接到的雨水
class Solution {
    public int trap(int[] height) {
        Stack<Integer> st = new Stack<>();
        int ans = 0;
        for (int i = 0; i < height.length; i++) {
            if (!st.isEmpty() && height[i] > height[st.peek()]) {
                int t = st.pop();
                if (st.isEmpty()) break;
                int l = i - st.peek() - 1;
                int r = Math.min(height[i], height[st.peek()]) - height[t];
                ans += l * r;
            }
            st.push(i);
        }
        return ans;
    }
}

43. 字符串相乘


  • 模拟
  • 字符串模拟乘法过程即可
class Solution {
    public String multiply(String num1, String num2) {
        if (num1.equals("0") || num2.equals("0")) return "0";
        int m = num1.length();
        int n = num2.length();
        int[] res = new int[m + n];  // 维护每一层乘法的结果
        for (int i = m - 1; i >= 0; i--) {
            int x = num1.charAt(i) - '0';
            for (int j = n - 1; j >= 0; j--) {
                int y = num2.charAt(j) - '0';
                res[i + j + 1] += x * y;  // 记录结果
            }
        }
        int base = 0;
        for (int i = m + n - 1; i >= 0; i--) {
            int sum = res[i] + base;
            res[i] = sum % 10;
            base = sum / 10;
        }
        StringBuilder sb = new StringBuilder();
        for (int num : res) sb.append(num);
        while (sb.length() > 0 && sb.charAt(0) == '0') sb.deleteCharAt(0);
        return sb.toString();
    }
}

44. 通配符匹配


  • dp

  • 第二题青春版

  • 状态表示:

    • dp[i][j] 表示字符串 s 的前 i 个字符和字符串 p 的前 j 个字符是否匹配。
    • 初始化 dp[0][0]true,表示空字符串和空模式是匹配的。
    • 初始化所有 * 之前的状态为 true,因为 * 可以匹配空字符串。
  • 状态计算:

    • 如果 p 的当前字符是 *,则可以选择匹配空字符串 dp[i][j-1] 或者匹配多个字符 dp[i-1][j],只要有一个为 true,则 dp[i][j] 也为 true
    • 如果 p 的当前字符是 ?,或者 p 的当前字符和 s 的当前字符相等,则将 dp[i][j] = dp[i-1][j-1],表示当前位置的匹配状态与前一个位置的匹配状态相同。
    • 否则,dp[i][j]false,表示当前位置的字符不匹配。
class Solution {
    public boolean isMatch(String s, String p) {
        int n = s.length(), m = p.length();
        boolean[][] dp = new boolean[n + 1][m + 1];
        dp[0][0] = true;
        // 初始化所有 * 之前的状态都为 true
        for (int i = 1; i <= m; i ++) {
            if (p.charAt(i - 1) == '*') dp[0][i] = true;
            else break;
        }
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= m; j ++) {
                if (p.charAt(j - 1) == '*') dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
                else if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1];
            }
        }
        return dp[n][m];
    }
}

45. 跳跃游戏 II


  • 贪心
  • 对于当前所处的位置 i,当 i + nums[i] >= n - 1 时可以直接返回结果
  • 否则,从 j = i 遍历到 j = i + nums[i],设下一步的位置为 res,以 res 能到达的最远位置为 idx
  • 显然, j + nums[j] 即为下一步可以到达的最远位置 idx
  • 则只需找到 j + nums[j] 的最大值,并用 res 维护下一步的坐标 j 即可
class Solution {
    public int jump(int[] nums) {
        int ans = 0, n = nums.length;
        if(n <= 1) return 0;  // 当长度只有 1 时无需操作
        for(int i = 0; i < n; i ++){
            int res = 0, idx = 0;
            if(i + nums[i] >= n - 1) return ans + 1;  // 下一步的最远距离正好到达终点
            for(int j = i + 1; j <= i + nums[i]; j ++){  // 遍历找到能到达最远距离的方案
                if(j + nums[j] >= idx){
                    idx = j + nums[j];
                    res = j;
                }
            }
            i = res - 1; ans ++;  // 更新 i 和 ans
        }
        return ans;
    }
}

46. 全排列


  • 排列
  • [31. 下一个排列](#31. 下一个排列) 题无修版(
  • 先将给出的数组按照从小到大排序,以最小字典序寻找下一个全排列
  • 找不到全排列直接返回
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList<>();
        ans.add(this.asList(nums));
        while (nextPermutation(nums)) ans.add(this.asList(nums));
        return ans;
    }

    public boolean nextPermutation(int[] nums) {
        int n = nums.length, i = n - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) i --;
        if (i >= 0) {
            int j = n - 1;
            while (nums[j] <= nums[i]) j --;
            swap(nums, i, j);
            this.reverse(nums, i + 1, n - 1);
            return true;
        }
        else return false;  // 找不到直接返回
    }

    private void swap(int[] nums, int i, int j) {
        int t = nums[i];
        nums[i] = nums[j]; nums[j] = t;
    }

    private void reverse(int[] nums, int l, int r) {
        while (l < r) {
            this.swap(nums, l, r);
            l ++; r --;
        }
    }

    private List<Integer> asList(int[] nums) {
        List<Integer> res = new ArrayList<>();
        for (int num : nums) res.add(num);
        return res;
    } 
}

47. 全排列 II


  • 排列
  • 和上一题一毛一样
class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList<>();
        ans.add(this.asList(nums));
        while (nextPermutation(nums)) ans.add(this.asList(nums));
        return ans;
    }

    public boolean nextPermutation(int[] nums) {
        int n = nums.length, i = n - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) i --;
        if (i >= 0) {
            int j = n - 1;
            while (nums[j] <= nums[i]) j --;
            swap(nums, i, j);
            this.reverse(nums, i + 1, n - 1);
            return true;
        }
        else return false;  // 找不到直接返回
    }

    private void swap(int[] nums, int i, int j) {
        int t = nums[i];
        nums[i] = nums[j]; nums[j] = t;
    }

    private void reverse(int[] nums, int l, int r) {
        while (l < r) {
            this.swap(nums, l, r);
            l ++; r --;
        }
    }

    private List<Integer> asList(int[] nums) {
        List<Integer> res = new ArrayList<>();
        for (int num : nums) res.add(num);
        return res;
    } 
}

48. 旋转图像


  • 矩阵转置
  • 用线性代数的知识,先将矩阵转置,再翻转即可
  • 将矩阵 A A A 的行换成同序数的列得到的新的矩阵,叫做 A A A 的转置矩阵,记作: A T A^T AT
  • 转置运算公式:
    )
class Solution {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        // 先进行矩阵的转置操作
        for (int i = 0; i < n; i ++) {
            for (int j = i; j < n; j ++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
        // 再进行每行的翻转操作
        for (int i = 0; i < n; i ++) {
            for (int j = 0; j < n / 2; j ++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[i][n - 1 - j];
                matrix[i][n - 1 - j] = temp;
            }
        }
    }
}

49. 字母异位词分组


  • 哈希表
  • 对于 strs[i] 将其按照字典序排序并映射下标,保存到 HashMap
  • 每次找到相同的排序后的映射,将其加入到答案 List[i] 后面即可
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        List<List<String>> ans = new ArrayList<>();
        HashMap<String, Integer> vis = new HashMap<>();
        int idx = 0;
        for (String s : strs) {
            char[] t = s.toCharArray();
            Arrays.sort(t);  // 得到字典序
            String st = new String(t);
            // 根据字典序 st 查找是否存在该映射 
            if (vis.containsKey(st)) ans.get(vis.get(st)).add(s);  // 存在则对应索引的 list add(s)
            else {
                // 不存在 创建新的 list
                List<String> newList = new ArrayList<>();
                newList.add(s); ans.add(newList);
                vis.put(st, idx ++);  // 记录映射
            }
        }
        return ans;
    }
}

50. Pow(x, n)


  • 快速幂
  • 板子题不会去看快速幂详解
class Solution {
    public double myPow(double x, int n) {
        if (x == 0.0d) return 0.0d;
        long k = n;
        double res = 1.0d;
        if (k < 0) {
            x = 1 / x;
            k = -k;
        }
        while (k > 0) {
            if ((k & 1) == 1) res *= x;
            x *= x;
            k >>= 1;
        }
        return res;
    }
}

51. N 皇后


  • DFS
  • 递归的每一层,只能放一次棋子
  • 对于每个棋子,必须保证其斜率 1 1 1 − 1 -1 1 的直线上和这一列上没有相同的棋子
  • 即对于棋子 [i][j] 在斜率为 1 1 1 − 1 -1 1 时判断其截距是否已经存在即可,且要求 j 列不存在相同的棋子
class Solution {
    public List<List<String>> solveNQueens(int n) {
        boolean[] col = new boolean[n];
        boolean[] l = new boolean[n * 2 + 1];
        boolean[] r = new boolean[n * 2 + 1];
        List<String> res = new ArrayList<>();
        List<List<String>> ans = new ArrayList<>();
        dfs(0, n, col, l, r, res, ans);
        return ans;
    }

    void dfs(int u, int n, boolean[] col, boolean[] l, boolean[] r, 
            List<String> res, List<List<String>> ans) {
        if (u == n) {
            ans.add(new ArrayList<>(res));
            return ;
        }
        char[] s = new char[n];
        for (int i = 0; i < n; i ++) s[i] = '.';
        for (int i = 0; i < n; i ++) {
            // 判断是否冲突
            if (!col[i] && !l[u - i + n] && !r[u + i]) {
                col[i] = l[u - i + n] = r[u + i] = true;  // u - i + n 防止 u - i < 0 越界
                s[i] = 'Q';
                res.add(new String(s));
                dfs (u + 1, n, col, l, r, res, ans);
                // 恢复现场
                res.remove(res.size() - 1);
                s[i] = '.';
                col[i] = l[u - i + n] = r[u + i] = false;
            }
        }
    }
}

52. N 皇后 II


  • DFS
  • 上一题青春版
  • 只需要维护方案数量即可,不需要记录答案
class Solution {
    private int ans;

    public int totalNQueens(int n) {
        boolean[] col = new boolean[n];
        boolean[] l = new boolean[n * 2 + 1];
        boolean[] r = new boolean[n * 2 + 1];
        return dfs (0, n, col, l, r);
    }

    int dfs(int u, int n, boolean[] col, boolean[] l, boolean[] r) {
        if (u == n) return 1;
        int res = 0;
        for (int i = 0; i < n; i ++) {
            // 判断是否冲突
            if (!col[i] && !l[u - i + n] && !r[u + i]) {
                col[i] = l[u - i + n] = r[u + i] = true;  // u - i + n 防止 u - i < 0 越界
                res += dfs (u + 1, n, col, l, r);
                col[i] = l[u - i + n] = r[u + i] = false;  // 恢复现场
            }
        }
        return res;
    }
}

53. 最大子数组和


  • dp
  • 状态表示:
    • dp[i] 表示以 nums[i] 结尾的数组中,最大的连续子数组的值
  • 状态计算:
    • dp[i] <= 0 说明前面的数组没有继续扩展的需要,以 dp[i] 结尾的最大值为 Math.max(dp[i - 1], nums[i])
    • 否则,最大值为 dp[i - 1] + nums[i]
class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        dp[0] = nums[0];    // 初始化
        int ans = nums[0];  // ans 维护 dp 的最大值结果
        for (int i = 1; i < n; i ++) {
            if (dp[i - 1] > 0) dp[i] = dp[i - 1] + nums[i];
            else dp[i] = Math.max(dp[i - 1], nums[i]);
            ans = Math.max(ans, dp[i]);
        }
        return ans;
    }
}

54. 螺旋矩阵


  • 模拟
  • 本质就是蛇形矩阵
  • 按照旋转的顺时针方向构建偏移量数组
  • l, r 为当前遍历位置的坐标:
    • l < 0 || r < 0 || l >= n || r >= m 说明越界,此时要按照顺时针方向转向
    • vis[l][r] 已经被标记过,说明之前已经走过这个位置,显然也是越界行为,也要转向
  • 遍历的步数为 n * m
class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        int n = matrix.length, m = matrix[0].length;
        boolean[][] vis = new boolean[n][m];    // 作为标记数组判断是否走过该点
        List<Integer> ans = new ArrayList<>();  // 记录答案
        int x = 0, y = 0, d = 0;
        int[] dx = {0, 1, 0, -1}, dy = {1, 0, -1, 0};  // 构建偏移量数组,d = 0 表示向 -> 走的偏移量
        for (int i = 0; i < n * m; i ++) {
            ans.add(matrix[x][y]); vis[x][y] = true;   // 将走过的点标记 
            int l = x + dx[d], r = y + dy[d];   	   // l 和 r 为下一步要走的点,此时进行判断
            if (l < 0 || r < 0 || r >= m || l >= n || vis[l][r]) {
                d = (d + 1) % 4; 			   // 得到下一个偏移量
                l = x + dx[d]; r = y + dy[d];  // 重新更新 l, r
            }
            x = l; y = r;  // 更新 x, y
        }
        return ans;
    }
}

55. 跳跃游戏


  • 模拟
  • 45 的青春版
  • idx 记录可以到达的最大位置的下标
  • 每次循环:
    • i > idx 说明当前位置 i 无法到达,直接 false
    • 更新 idx = Math.max(idx, i + nums[i])
    • idx >= nums.length - 1 说明已经可以到达尾部,直接 true
class Solution {
    public boolean canJump(int[] nums) {
        int idx = 0;
        for (int i = 0; i < nums.length; i++) {
            if (i > idx) return false; // 当前位置无法到达
            idx = Math.max(idx, i + nums[i]);
            if (idx >= nums.length - 1) {
                return true; // 已经可以到达最后一个位置
            }
        }
        return false;
    }
}

56. 合并区间


  • 区间合并
  • 板子题
  • 对区间左端点排序,判断前一个区间的右端点和当前区间的左端点关系
  • 若前一个区间的右端点小于等于当前区间左端点,说明可以合并,更新右端点
  • 否则当前区间为新的区间
class Solution {
    public int[][] merge(int[][] intervals) {
        int n = intervals.length;
        Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);  // 按照左端点从小到大排序
        List<int[]> ans = new ArrayList<>();
        int l = intervals[0][0], r = intervals[0][1];
        for (int i = 1; i < n; i ++) {
            // 合并操作
            if (intervals[i][0] <= r) r = Math.max(intervals[i][1], r);
            else {
                ans.add(new int[]{l, r});
                l = intervals[i][0]; r = intervals[i][1];
            }
        }
        ans.add(new int[]{l, r});  // 将最后一组区间加入答案
        return ans.toArray(new int[ans.size()][]);
    }
}

57. 插入区间


  • 思路和上一题一样
  • 把新区间加进来重新排序即可
class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        int[][] res = new int[intervals.length + 1][2];
        for (int i = 0; i < intervals.length; i ++) res[i] = intervals[i];
        res[intervals.length] = newInterval;
        intervals = res;
        Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);
        List<int[]> ans = new ArrayList<>();
        int l = intervals[0][0], r = intervals[0][1];
        for (int i = 1; i < intervals.length; i ++) {
            // 合并操作
            if (intervals[i][0] <= r) r = Math.max(intervals[i][1], r);
            else {
                ans.add(new int[]{l, r});
                l = intervals[i][0]; r = intervals[i][1];
            }
        }
        ans.add(new int[]{l, r});  // 将最后一组区间加入答案
        return ans.toArray(new int[ans.size()][]);
    }
}

58. 最后一个单词的长度


  • 模拟
  • 找到第一个不是 的位置,然后统计即可
class Solution {
    public int lengthOfLastWord(String s) {
        int cnt = 0;
        char op = 'a';
        int idx = s.length() - 1;
        while (idx >= 0 && s.charAt(idx) == ' ') idx --;
        while (idx >= 0) {
            op = s.charAt(idx);
            if ((op >= 'a' && op <= 'z') || (op >= 'A' && op <= 'Z')) cnt ++;
            else return cnt;
            idx --;
        }
        return cnt;
    }
}

59. 螺旋矩阵 II


  • 54 青春版
  • 甚至还不需要开 vis
class Solution {
    public int[][] generateMatrix(int n) {
        int[] dx = {0, 1, 0, -1}, dy = {1, 0, -1, 0};
        int l = 0, r = 0, d = 0;
        int[][] ans = new int[n][n];
        for (int i = 1; i <= n * n; i ++) {
            ans[l][r] = i;
            int x = l + dx[d], y = r + dy[d];
            if (x < 0 || y < 0 || x >= n || y >= n || ans[x][y] != 0) {
                d = (d + 1) % 4;
                x = l + dx[d]; y = r + dy[d];
            }
            l = x; r = y;
        }
        return ans;
    }
}

60. 排列序列


  • 全排列
class Solution {
    public String getPermutation(int n, int k) {
        char[] nums = new char[n];
        for (int i = 0; i < n; i ++) nums[i] = (char)(i + 1 + '0');
        while (k -- > 1) nextPermutation(nums);
        return String.valueOf(nums);
    }

    public void nextPermutation(char[] nums) {
        int n = nums.length;
        int i = n - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) i --;
        if (i >= 0) {
            int j = n - 1;
            while (nums[j] <= nums[i]) j --;
            swap(nums, i, j);
        }
        reverse(nums, i + 1, n - 1);
    }

    private void swap(char[] nums, int i, int j) {
        char t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }

    private void reverse(char[] nums, int l, int r) {
        while (l < r) {
            this.swap(nums, l, r);
            l ++; r --;
        }
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1150507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pytorch 入门 (五)案例三:乳腺癌识别-VGG16实现

本文为&#x1f517;小白入门Pytorch内部限免文章 &#x1f368; 本文为&#x1f517;小白入门Pytorch中的学习记录博客&#x1f366; 参考文章&#xff1a;【小白入门Pytorch】乳腺癌识别&#x1f356; 原作者&#xff1a;K同学啊 在本案例中&#xff0c;我将带大家探索一下深…

高压放大器在工作中的应用实例有哪些

高压放大器是一种电子设备&#xff0c;主要用于将输入的低电压信号放大为输出的高电压信号。它在工业、医疗、科研等领域具有广泛的应用。下面将列举一些高压放大器在工作中的应用实例&#xff1a; 1.高压发生器&#xff1a;高压放大器在高压发生器中被广泛使用。这些发生器通常…

Android渲染流程

目录 缓冲区的不同生命周期代表当前缓冲区的状态&#xff1a; 多个源 ViewRootImpl&#xff1a; Android4.0&#xff1a; Android5.0&#xff1a; Android应用程序调用SurfaceFliger将测量&#xff0c;布局&#xff0c;绘制好的Surface借助GPU渲染显示到屏幕上。 一个Acti…

Cannot find namespace ‘NodeJS‘.

最近在使用自定义指令做防抖节流的时候&#xff0c;使用ts定义定时器类型的时候报的错误&#xff0c;记录一下&#xff1a; 解决方法&#xff1a; 在根目录下找到env.d.ts&#xff0c;或者在根目录中新建一个文件&#xff08;global..d.ts&#xff09;在文件中导出NodeJS命名空…

OpenLayers入门,OpenLayers从vue的assets资源路径加载TopoJson文件并解析数据叠加到地图上,以加载世界各国边界为例

专栏目录: OpenLayers入门教程汇总目录 前言 本章以加载世界各国边界的TopoJson格式数据为例,讲解如何使用OpenLayers从vue的assets资源路径加载TopoJson文件并解析数据叠加到地图上。 GeoJson介绍 GEOJSON是gis地图中常用的数据格式,制作地图时用于存储各种地理数据,使…

使用 node.js 简单搭建Web服务 使用node简单搭建后端服务 使用node搭建服务

使用 node.js 简单搭建Web服务 使用node简单搭建后端服务 使用node搭建服务 1、初始化项目2、安装 Express.js Web 服务框架3、创建 app.js 主入口文件, 并且实现 GET、POST请求4、启动服务5、请求测试 1、初始化项目 例如项目名为 node-server-demo mkdir node-server-demo进…

sd 模型笔记之 SDXL

C站有个创作者Copax&#xff0c;他创作了许多模型和LoRA&#xff0c;其中有一个SDXL1.0的模型非常受欢迎&#xff0c;叫Copax TimeLessXL。 TimeLess的字面意思是永恒&#xff0c;不过有一首英文歌也是TimeLess&#xff0c;歌词大意是唯爱永恒。 这个SDXL1.0的模型更新到了8…

Azure机器学习 - 使用与Azure集成的Visual Studio Code实战教程

本文介绍如何启动远程连接到 Azure 机器学习计算实例的 Visual Studio Code。 借助 Azure 机器学习资源的强大功能&#xff0c;使用 VS Code 作为集成开发环境 (IDE)。 在VS Code中将计算实例设置为远程 Jupyter Notebook 服务器。 关注TechLead&#xff0c;分享AI全维度知识。…

从历史的探索到RFID固定资产管理的未来

在人类历史上&#xff0c;技术的进步一直是推动社会和工业发展的关键因素。其中&#xff0c;RFID技术的出现是一个重要的里程碑。让我们回顾一下RFID技术的历史&#xff0c;并探讨如何将其应用于固定资产管理&#xff0c;为企业提供更高效、智能的解决方案。 RFID&#xff08;R…

开发一个简单的管理系统,前端选择 Vue 还是 React?

目录 比于React&#xff0c;我更喜欢使用Vue 低代码平台的前端框架采用Vue的优势有哪些&#xff1f; JNPF-Web-Vue3 的技术栈介绍 &#xff08;1&#xff09;Vue3.x &#xff08;2&#xff09;Vue-router4.x &#xff08;4&#xff09;Ant-Design-Vue3.x &#xff08;5&#xf…

安装pip install pointnet2_ops_lib/.

安装这个的时候一直报错 总结原因还是版本不一致的问题&#xff0c;参考的博客https://blog.csdn.net/weixin_45144684/article/details/132525431 最后在这个链接里面https://anaconda.org/nvidia/cuda-toolkit成果安装11.5的粗大tookit就成功了&#xff0c; 最后附上所有包的…

大数据-Storm流式框架(七)---Storm事务

storm 事务 需求 storm 对于保证消息处理&#xff0c;提供了最少一次的处理保证。最常见的问题是如果元组可以被 重发&#xff0c;可以用于计数吗&#xff1f;不会重复计数吗&#xff1f; strom0.7.0 引入了事务性拓扑的概念&#xff0c;可以保证消息仅被严格的处理一次。因此可…

JMeter的使用,傻瓜式学习【上】

目录 前言 1、JMeter元件及基本使用作用域&#xff08;简述&#xff09; 1.1、基本元件 1.2、作用域的原则 1.3、元件执行顺序 2、JMeter三个重要组件 2.1、线程组 案例&#xff1a; 2.2、HTTP请求 2.3、查看结果树 响应体中&#xff0c;中文乱码解决方案&#xff1…

python爬虫—使用xpath方法进行数据解析

1. 背景信息 爬取安居客二手房源信息 URL地址&#xff1a;https://wuhan.anjuke.com/sale/?fromnavigation 2. 代码实现 import requests from lxml import etreeif __name__ __main__:# 1.指定URLurl "https://wuhan.anjuke.com/sale/?fromnavigation"# 2.U…

c语言字符类型及其大小

c语言字符类型及其大小 c语言字符类型及其大小 c语言字符类型及其大小一、整形int类型二、字符型char类型三、浮点型四、类型限定符五、sizeof()函数六、32操作系统字符类型大小比较 一、整形int类型 二、字符型char类型 三、浮点型 四、类型限定符 五、sizeof()函数 sizeof&a…

Python + RobotFramework 测试框架分享二(项目实践)

项目介绍: 本篇文章是Python + RobotFramework测试框架分享的第二篇文章,介绍的是基于RobotFframework+python构建的一个射频自动化测试工具。包含WIFI的射频测试,BT的射频测试以及校准功能的一个工具。 项目背景: RF: 也就是常说的射频,英文:Radio Ffrequency WIFI:…

软测推荐第二期:10本高质量测试书籍

在不断发展的软件开发领域&#xff0c;测试是质量的守护者&#xff0c;确保产品不仅满足功能要求&#xff0c;而且提供无缝的用户体验。随着软件复杂性的增加&#xff0c;对完善的测试方法和见解的需求也随之增加。 上次给大家推荐了五本书&#xff0c;获得了大家的积极反馈&a…

【C语言】realloc()函数详解(动态内存开辟函数)

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:C语言 ⚙️操作环境:Visual Studio 2022 一.realloc()函数简介 我们先来看一下cplusplus.com - The C Resources Network网站上realloc()函数的基本信息&#xff1a; 1.函数功能 可以看到,realloc()函数的功能是:更改动…

请教设计师全屋定制有哪些门道

⬛装修很难绕开全屋定制&#xff0c;设计师来告诉你有哪些前期就要留意的事项&#x1f320;⬛全屋定制板材&#x1f4da;常用的板材&#xff1a;颗粒板、密度板、生态板、多层板、欧松板、实木指接板’对于家装来说&#xff0c;建议大家选大品牌和环保达标的。常用板材的厚度&a…

信锐-框式交换机基础运维命令

目录 一&#xff0c;telnet登录配置 二&#xff0c;ssh登录配置 三&#xff0c;Web登录 四&#xff0c;S框模式切换 五&#xff0c;时间配置 六&#xff0c;用户和密码设置 七&#xff0c;config模式 八&#xff0c;查看交换机版本 九&#xff0c;查看板卡信息 十&am…