编程练习题(4)

搜索二维矩阵

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。

Java解法:

public static boolean searchMatrix(int[][] matrix, int target) {
    
    
        int m = matrix.length, n = matrix[0].length;
        int i = 0, j = n -1;
        while (i < m && j >= 0){
    
    
            if (matrix[i][j] < target){
    
    
                i++;
            }else if (matrix[i][j] > target){
    
    
                j--;
            }else{
    
    
                return true;
            }
        }
        return false;
    }

Python解法:

# 搜索二维矩阵
class Solution:
    def searchMatrix(self, matrix: list, target: int) -> bool:
        i, j, n = 0, len(matrix[0])-1, len(matrix)
        while i < n and j >= 0:
            if matrix[i][j] < target:
                i += 1
            elif matrix[i][j] > target:
                j -= 1
            else:
                return True
        return False

# 测试
s = Solution()
matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]]
target = 3
print(s.searchMatrix(matrix, target))

# 答案:
True

颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

Java解法:

/*
双指针
*/
 public static void sortColors(int[] nums) {
    
    
        int cur = 0, left = 0, right = nums.length-1;
        while (cur <= right){
    
    
            /*0,就与左边换*/
            if (nums[cur] == 0){
    
    
                int tmp = nums[left];
                nums[left++] = nums[cur];
                nums[cur++] = tmp;
            /*1,就与右边换*/
            }else if (nums[cur] == 2){
    
    
                int tmp = nums[cur];
                nums[cur] = nums[right];
                nums[right--] = tmp;
            }else {
    
    
                cur++;
            }
        }
    }

Python解法:

# 颜色分类
# 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
#
# 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
class Solution:
    def sortColors(self, nums: list) -> None:
        left, cur, right = 0, 0, len(nums) - 1
        while cur <= right:
            # 为0,与左边换
            if nums[cur] == 0:
                nums[left], nums[cur] = nums[cur], nums[left]
                left += 1
                cur += 1
            # 为2,与右边换
            elif nums[cur] == 2:
                nums[right], nums[cur] = nums[cur], nums[right]
                right -= 1
            else:
                cur += 1

# 测试
s = Solution()
nums = [2,0,2,1,1,0]
s.sortColors(nums)
print(nums)

# 测试
[0, 0, 1, 1, 2, 2]

滑动窗口

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案

Java解法:

 /*
    滑动窗口
    方法:整型数组存放 Char, Char 的 int 值范围为 0 ~ 127
          利用数组 window 存放窗口中字符个数
          利用数组 need 存放匹配子串中需要的字符个数
          有两个指针。一个用于「延伸」现有窗口的 r 指针,和一个用于「收缩」窗口的 l 指针。
          在任意时刻,只有一个指针运动,而另一个保持静止。
          我们在 s 上滑动窗口,通过移动 r 指针不断扩张窗口。
          当窗口包含 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口

    */


    public static String minWindow(String s, String t) {
    
    
        /* 如果字符串为空,或者长度小于需要匹配的长度 */
        if(s.length() == 0 || t.length() == 0 || s.length() < t.length()){
    
    
            return "";
        }
        int[] need = new int[128];
        int[] window = new int[128];

        /* 窗口中已经匹配的字符个数 */
        int count = 0;
        int left = 0;
        int right = 0;
        int minLength = s.length();
        String res = "";

        /* need 初始化,统计字符 */
        for(int i = 0; i < t.length(); i ++ ){
    
    
            need[t.charAt(i)] ++;
        }

        while(right < s.length()){
    
    
            char c = s.charAt(right);
            window[c]++;

            /* 如果需要该字符,并且已有窗口内的字符个数 小于需要的字符个数 */
            if(need[c] > 0 && need[c] >= window[c]){
    
    
                count ++;
            }

            /* 当需要的字符都已经包含在窗口中后,开始收缩 left */
            while(count == t.length()){
    
    
                char ch = s.charAt(left);
                /* 当需要删除的字符,是必须留在窗口内时 */
                if(need[ch] > 0 && need[ch] == window[ch]){
    
    
                    count --;
                }
                /* 这边需要取 = ,因为可能一开始两个字符串就是匹配的,如 a , a return a */
                if(right - left + 1 <= minLength){
    
    
                    minLength = right - left + 1;
                    res = s.substring(left, right + 1);
                }
                window[ch] --;
                left ++;
            }
            right++;
        }
        return res;
    }

Python解法:

# 最小覆盖子串
# 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
# 注意:如果 s 中存在这样的子串,我们保证它是唯一的答案

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        if len(s) == 0 or len(t) == 0 or len(s) < len(t):
            return ''
        need = [0 for _ in range(128)]
        window = [0 for _ in range(128)]
        count = left = right = 0
        minlength = len(s)
        res = ''

        # need 初始化,统计字符, ord将字符转数字
        for i in range(0, len(t)):
            need[ord(t[i])] += 1

        while right < len(s):
            c = ord(s[right])
            window[c] += 1

            # 如果需要该字符,并且已有窗口内的字符个数 小于需要的字符个数
            if need[c] > 0 and need[c] >= window[c]:
                count += 1

            # 当需要的字符都已经包含在窗口中后,开始收缩 left
            while count == len(t):
                ch = ord(s[left])
                # 当需要删除的字符,是必须留在窗口内时
                if need[ch] > 0 and need[ch] == window[ch]:
                    count -= 1
                # 这边需要取 = ,因为可能一开始两个字符串就是匹配的,如 a , a return a
                if right - left + 1 <= minlength:
                    minlength = right - left + 1
                    res = ''.join(s[left:right+1])

                window[ch] -= 1
                left += 1
            right += 1

        return res


# 测试
test = Solution()
s = "ADOBECODEBANC"
t = "ABC"
print(test.minWindow(s, t))

# 答案:
BANC

组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

Java解法:

public static List<List<Integer>> combine(int n, int k) {
    
    
        if (n == 0){
    
    
            return new ArrayList<>();
        }
        List<List<Integer>> res = new ArrayList<>();
        backTrack(res, new ArrayList<Integer>(), n, k, 1);
        return res;
    }

    public static void backTrack(List<List<Integer>> res, ArrayList<Integer> list, int n, int k, int val){
    
    
        if (list.size() == k){
    
    
            res.add(new ArrayList<>(list));
            return;
        }
        for (int i = val ; i <= n ; i++){
    
    
            if(!list.contains(i)) {
    
    
                list.add(i);
                backTrack(res, list, n, k, i+1);
                list.remove(list.size()-1);
            }
        }
    }

Python解法:

# 组合
class Solution:
    def combine(self, n: int, k: int) -> list:
        if n == 0 or n < k:
            return []
        res = []
        self.backTrack(res, [], n, k, 1)
        return res

    def backTrack(self, res: list, cur: list, n: int, k: int, val: int):
        if len(cur) == k:
            res.append(cur[:])
            return
        for i in range(val, n+1):
            if i not in cur:
                cur.append(i)
                self.backTrack(res, cur, n, k, i+1)
                cur.remove(i)

# 测试
s = Solution()
n = 4
k = 2
print(s.combine(n, k))

# 答案:
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

Java解法:

	/*
    *   子集
    */
    public static List<List<Integer>> subsets(int[] nums) {
    
    
        List<List<Integer>> res = new ArrayList<>();
        for (int i = 0 ; i <= nums.length ; i++){
    
    
            backTrack(res, new ArrayList<Integer>(), 0, i, nums);
        }
        return res;
    }

    public static void backTrack(List<List<Integer>> res, ArrayList<Integer> list, int begin, int end, int []nums){
    
    
        if (list.size() == end){
    
    
            res.add(new ArrayList<>(list));
            return;
        }
        for (int i = begin; i < nums.length; i++){
    
    
            list.add(nums[i]);
            backTrack(res, list, i+1, end, nums);
            list.remove(list.size()-1);
        }
    }

Python解法:

# 子集
class Solution:
    def subsets(self, nums: list) -> list:
        if not nums:
            return []
        res = []
        for i in range(len(nums)+1):
            self.backTrack(res, [], 0, i, nums)
        return res

    def backTrack(self, res: list, cur: list, begin: int, end: int, nums: list):
        if len(cur) == end:
            res.append(cur[:])
            return
        for i in range(begin, len(nums)):
            cur.append(nums[i])
            self.backTrack(res, cur, i+1, end, nums)
            cur.remove(nums[i])

# 测试
s = Solution()
nums = [1,2,3]
print(s.subsets(nums))

# 答案:
[[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]]

单词搜索

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

Java解法:

 /*
    *   回溯
    */

    public static boolean exist(char[][] board, String word) {
    
    
        if (board == null || board.length == 0){
    
    
            return false;
        }
        int m = board.length, n = board[0].length;
        for (int i = 0 ; i < m ; i++){
    
    
            for (int j = 0 ; j < n ; j++){
    
    
                if (backTrack(board, m, n, i, j, word, 0)){
    
    
                    return true;
                }
            }
        }
        return false;
    }

    public static boolean backTrack(char [][]board, int m, int n, int i, int j, String word, int k){
    
    
        if (k == word.length()){
    
    
            return true;
        }
        if (i < 0 || i >= m || j < 0 || j >= n || board[i][j] != word.charAt(k)){
    
    
            return false;
        }
        char cur = board[i][j];
        board[i][j] = '#';
        if (backTrack(board, m, n, i+1, j, word, k+1)){
    
    
            return true;
        }
        if (backTrack(board, m, n, i-1, j, word, k+1)){
    
    
            return true;
        }
        if (backTrack(board, m, n, i, j+1, word, k+1)){
    
    
            return true;
        }
        if (backTrack(board, m, n, i, j-1, word, k+1)){
    
    
            return true;
        }
        board[i][j] = cur;
        return false;
    }

Python解法:

# 单词搜索
class Solution:
    def exist(self, board: list, word: str) -> bool:
        if not board or len(board) == 0:
            return False
        m, n = len(board), len(board[0])
        for i in range(m):
            for j in range(n):
                if self.backTrack(board, m, n, i, j, word, 0):
                    return True
        return False

    def backTrack(self, board: list, m: int, n: int, i: int, j: int, word: str, k: int) -> bool:
        if len(word) == k:
            return True
        if i < 0 or i >= m or j < 0 or j >= n or word[k] != board[i][j]:
            return False
        cur = board[i][j]
        board[i][j] = '#'
        if self.backTrack(board, m, n, i+1, j, word, k+1):
            return True
        if self.backTrack(board, m, n, i-1, j, word, k+1):
            return True
        if self.backTrack(board, m, n, i, j+1, word, k+1):
            return True
        if self.backTrack(board, m, n, i, j-1, word, k+1):
            return True
        board[i][j] = cur
        return False

# 测试
s = Solution()
board =[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]
arr = "SEE"
print(s.exist(board, arr))

# 答案:
True

删除排序数组中的重复项 II

给定一个增序排列数组 nums ,你需要在 原地 删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。

Java解法:

 /*
    使用了两个指针,i 是遍历指针,指向当前遍历的元素;p 指向下一个要覆盖元素的位置。
     */

    public static int removeDuplicates(int[] nums) {
    
    
        if (nums == null){
    
    
            return 0;
        }
        int count = 1, p = 1;
        for (int i = 1 ; i < nums.length ; i++){
    
    
            if (nums[i] == nums[i-1]){
    
    
                count++;
            }else{
    
    
                count = 1;
            }
            if (count < 3){
    
    
                nums[p++] = nums[i];
            }
        }
        return p;
    }

Python解法:

# 删除排序数组中的重复项 II
class Solution:
    def removeDuplicates(self, nums: list) -> int:
        if not nums:
            return 0
        count = p = 1
        for i in range(1, len(nums)):
            if nums[i] == nums[i-1]:
                count += 1
            else:
                count = 1
            if count < 3:
                nums[p] = nums[i]
                p += 1
        return p

# 测试
s = Solution()
nums = [1,1,1,2,2,3]
print(s.removeDuplicates(nums))

# 答案:
5

搜索旋转排序数组 II

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

Java解法:

public static boolean search(int[] nums, int target) {
    
    
        if(nums == null || nums.length == 0){
    
    
            return false;
        }
        int n = nums.length;
        int left = 0, right = n-1;
        while (left <= right){
    
    
            int mid = (right - left) / 2 + left;
            if (nums[mid] == target){
    
    
                return true;
            }
            if (nums[left] == nums[mid]){
    
    
                left++;
                continue;
            }
            /*前半部分有序*/
            if (nums[left] < nums[mid]){
    
    
                if (nums[left] <= target && target < nums[mid]){
    
    
                    right = mid - 1;
                }else{
    
    
                    left = mid + 1;
                }
            }else{
    
    
                if (nums[mid] < target && target <= nums[right]){
    
    
                    left = mid + 1;
                }else{
    
    
                    right = mid - 1;
                }
            }
        }
        return false;
    }

Python解法:

# 搜索旋转排序数组 II
class Solution:
    def search(self, nums: list, target: int) -> bool:
        if not nums or len(nums) == 0:
            return False
        left, right = 0, len(nums)-1
        while left <= right:
            mid = (right - left) // 2 + left
            if nums[mid] == target:
                return True
            if nums[left] == nums[mid]:
                left += 1
                continue
            # 前半部分有序
            if nums[left] < nums[mid]:
                if nums[left] <= target < nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                if nums[mid] < target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
        return False

# 测试
s = Solution()
nums = [2,5,6,0,0,1,2]
target = 0
print(s.search(nums, target))

# 答案:
True

删除排序链表中的重复元素 II

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

Java解法:

 public static ListNode deleteDuplicates(ListNode head) {
    
    
        if(head == null || head.next == null){
    
    
            return head;
        }
        ListNode root = new ListNode(-1);
        root.next = head;
        ListNode first = root, second = head;
        while (second != null && second.next != null){
    
    
            if (first.next.val != second.next.val){
    
    
                first = first.next;
                second = second.next;
            }else{
    
    
                while (second.next != null && second.next.val == first.next.val){
    
    
                    second = second.next;
                }
                first.next = second.next;
                second = second.next;
            }
        }
        return root.next;
    }

柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。

Java解法:

 /*
    柱状图中最大的矩形
    给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
    求在该柱状图中,能够勾勒出来的矩形的最大面积。

    单调栈 + 常数优化:从左往右对数组进行遍历,借助单调栈求出了每根柱子的左右边界
     */
    public static int largestRectangleArea(int[] heights) {
    
    
        int n = heights.length;
        int []left = new int[n];
        int []right = new int[n];
        Arrays.fill(right,n);
        Stack<Integer> stack = new Stack<>();
        for (int i = 0 ; i < n ; i++){
    
    
            while (!stack.isEmpty() && heights[stack.peek()] >= heights[i] ){
    
    
                right[stack.peek()] = i;
                stack.pop();
            }
            left[i] = stack.isEmpty() ? -1 : stack.peek();
            stack.push(i);
        }
        int res = 0;
        for (int i = 0 ; i < n ; i++){
    
    
            res = Math.max(res, (right[i] - left[i] - 1) * heights[i]);
        }
        return res;
    }

Python解法:

# 柱状图中最大的矩形
#     给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
#     求在该柱状图中,能够勾勒出来的矩形的最大面积。
class Solution:
    def largestRectangleArea(self, heights: list) -> int:
        n = len(heights)
        left = [0 for _ in range(n)]
        right = [n for _ in range(n)]
        stack = list()
        for i in range(n):
            while stack and heights[stack[-1]] >= heights[i]:
                right[stack[-1]] = i
                stack.pop()
            left[i] = stack[-1] if stack else -1
            stack.append(i)
        res = 0
        for i in range(n):
            res = max(res, (right[i] - left[i] - 1) * heights[i])
        return res

# 测试
s = Solution()
heights = [2,1,5,6,2,3]
print(s.largestRectangleArea(heights))

# 答案:
10

最大矩形

给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其
面积

Java解法:

	/*
    将输入拆分成一系列的柱状图。为了计算矩形的最大面积,我们只需要计算每个柱状图中的最大面积,并找到全局最大值
     */

    public static int maximalRectangle(char[][] matrix) {
    
    
        if (matrix == null || matrix.length == 0){
    
    
            return 0;
        }
        int m = matrix.length;
        int n = matrix[0].length;
        int [][]left = new int[m][n];

        for(int i = 0 ; i < m ; i++) {
    
    
            for (int j = 0 ; j < n ; j++){
    
    
                if (matrix[i][j] == '1'){
    
    
                    left[i][j] = (j == 0 ? 0 : left[i][j-1]) + 1;
                }
            }
        }

        int ret = 0;
        // 对于每一列,使用基于柱状图的方法
        for (int j = 0; j < n; j++) {
    
    
            int[] up = new int[m];
            int[] down = new int[m];
            Deque<Integer> stack = new LinkedList<>();
            for (int i = 0; i < m; i++) {
    
    
                while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
    
    
                    stack.pop();
                }
                up[i] = stack.isEmpty() ? -1 : stack.peek();
                stack.push(i);
            }
            stack.clear();
            for (int i = m - 1; i >= 0; i--) {
    
    
                while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
    
    
                    stack.pop();
                }
                down[i] = stack.isEmpty() ? m : stack.peek();
                stack.push(i);
            }
            for (int i = 0; i < m; i++) {
    
    
                int height = down[i] - up[i] - 1;
                int area = height * left[i][j];
                ret = Math.max(ret, area);
            }
        }
        return ret;
    }

Python解法:

# 最大矩形
# 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

class Solution:
    def maximalRectangle(self, matrix: list) -> int:
        if not matrix or len(matrix) == 0:
            return 0
        m, n = len(matrix), len(matrix[0])
        left = [[0 for _ in range(n)] for _ in range(m)]

        # 构造柱状图
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == '1':
                    left[i][j] = (left[i][j-1] if j != 0 else 0) + 1

        res = 0
        # 每一列,用求最大柱状图的方法
        for j in range(n):
            up = [0 for _ in range(m)]
            down = [0 for _ in range(m)]
            stack = list()

            for i in range(m):
                while stack and left[stack[-1]][j] >= left[i][j]:
                    stack.pop()
                up[i] = stack[-1] if stack else -1
                stack.append(i)

            stack.clear()

            for i in range(m-1, -1, -1):
                while stack and left[stack[-1]][j] >= left[i][j]:
                    stack.pop()
                down[i] = stack[-1] if stack else m
                stack.append(i)

            for i in range(m):
                height = down[i] - up[i] - 1
                area = height * left[i][j]
                res = max(res, area)

        return res

# 测试
s = Solution()
matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
print(s.maximalRectangle(matrix))

# 答案:
6

分隔链表

给你一个链表和一个特定值 x ,请你对链表进行分隔,使得所有小于 x 的节点都出现在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

Java解法:

/*
    维护两个链表small 和 large 即可,small 链表按顺序存储所有小于 x 的节点,
    large 链表按顺序存储所有大于等于 x 的节点。遍历完原链表后,只要将 small 链表尾节点指向 large 链表的头节点
    即能完成对链表的分隔。
     */

    public ListNode partition(ListNode head, int x) {
    
    
        ListNode small = new ListNode(0), large = new ListNode(0);
        ListNode small_head = small, large_head = large;
        while (head != null){
    
    
            if (head.val < x){
    
    
                small.next = head;
                small = small.next;
            }else{
    
    
                large.next = head;
                large = large.next;
            }
            head = head.next;
        }
        large.next = null;
        small.next = large_head.next;
        return small_head.next;
    }

扰乱字符串

给定一个字符串 s1,我们可以把它递归地分割成两个非空子字符串,从而将其表示为二叉树。
在扰乱这个字符串的过程中,我们可以挑选任何一个非叶节点,然后交换它的两个子节点。
给出两个长度相等的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串。

Java解法:

    /*
    思路:递归
    给定两个字符串 T 和 S,假设 T 是由 S 变换而来
        如果 T 和 S 长度不一样,必定不能变来
        如果长度一样,顶层字符串 S 能够划分为 S1 S2,同样字符串 T 也能够划分为 T1 T2
        情况一:没交换,S1 ==> T1,S2 ==> T2
        情况二:交换了,S1 ==> T2,S2 ==> T1
        子问题就是分别讨论两种情况,T1是否由 S1变来,T2是否由 S2 变来,或 T1是否由 S2变来,T2是否由 S1变来。
    得到状态:
        dp[i][j][k][h] 表示 T[k..h]是否由 S[i..j]变来。
        由于变换必须长度是一样的,因此这边有个关系 j - i = h - k,可以把四维数组降成三维。
        dp[i][j][len] 表示从字符串 S 中 i 开始长度为 len 的字符串是否能变换为从字符串 T 中 j 开始长度为 len 的字符串

        dp[i][j][k] = (1 <= w <= k−1) { dp[i][j][w] && dp[i+w][j+w][k-w]}
                    or (1 <= w <= k−1) { dp[i][j+k-w][w] && dp[i+w][j][k-w]}
        解释下:枚举 S1长度 w(从 1~k-1,因为要划分),f[i][j][k] 表示 S1能变成 T1
             ,f[i+w][j+w][k-w]表示 S2能变成 T2,或者是 S1 能变成 T2,S2能变成 T1
    初始条件
        对于长度是 1 的子串,只有相等才能变过去,相等为 true,不相等为 false。
     */

    public static boolean isScramble(String s1, String s2) {
    
    
        if (s1.length() != s2.length()){
    
    
            return false;
        }
        char []ch1 = s1.toCharArray();
        char []ch2 = s2.toCharArray();
        int n = ch1.length;

        boolean[][][] dp = new boolean[n][n][n + 1];
        /* 初始化单个字符的情况 */
        for (int i = 0; i < n; i++) {
    
    
            for (int j = 0; j < n; j++) {
    
    
                dp[i][j][1] = ch1[i] == ch2[j];
            }
        }

        /* 枚举区间长度 2~n */
        for (int len = 2; len <= n; len++) {
    
    
            /* 枚举 S 中的起点位置 */
            for (int i = 0 ; i <= n - len; i++){
    
    
                /* 枚举 T 中的起点位置 */
                for (int j = 0; j <= n - len ; j++){
    
    
                    /* 枚举划分位置 */
                    for (int k = 1 ; k <= len - 1; k++){
    
    
                        /* 第一种情况:S1 -> T1, S2 -> T2 */
                        if (dp[i][j][k] && dp[i+k][j+k][len-k]){
    
    
                            dp[i][j][len] = true;
                            break;
                        }
                        /* S1 -> T2, S2 -> T1 */
                        /*  S1 起点 i,T2 起点 j + 前面那段长度 len-k ,S2 起点 i + 前面长度k */
                        if (dp[i][j+len-k][k] && dp[i+k][j][len-k]){
    
    
                            dp[i][j][len] = true;
                            break;
                        }
                    }
                }
            }
        }

        return dp[0][0][n];
    }

Python解法:

# 思路:递归
#     给定两个字符串 T 和 S,假设 T 是由 S 变换而来
#         如果 T 和 S 长度不一样,必定不能变来
#         如果长度一样,顶层字符串 S 能够划分为 S1 S2,同样字符串 T 也能够划分为 T1 T2
#         情况一:没交换,S1 ==> T1,S2 ==> T2
#         情况二:交换了,S1 ==> T2,S2 ==> T1
#         子问题就是分别讨论两种情况,T1是否由 S1变来,T2是否由 S2 变来,或 T1是否由 S2变来,T2是否由 S1变来。
#     得到状态:
#         dp[i][j][k][h] 表示 T[k..h]是否由 S[i..j]变来。
#         由于变换必须长度是一样的,因此这边有个关系 j - i = h - k,可以把四维数组降成三维。
#         dp[i][j][len] 表示从字符串 S 中 i 开始长度为 len 的字符串是否能变换为从字符串 T 中 j 开始长度为 len 的字符串
#
#         dp[i][j][k] = (1 <= w <= k−1) { dp[i][j][w] && dp[i+w][j+w][k-w]}
#                     or (1 <= w <= k−1) { dp[i][j+k-w][w] && dp[i+w][j][k-w]}
#         解释下:枚举 S1长度 w(从 1~k-1,因为要划分),f[i][j][k] 表示 S1能变成 T1
#              ,f[i+w][j+w][k-w]表示 S2能变成 T2,或者是 S1 能变成 T2,S2能变成 T1
#     初始条件
#         对于长度是 1 的子串,只有相等才能变过去,相等为 true,不相等为 false。
class Solution:
    def isScramble(self, s1: str, s2: str) -> bool:
        if len(s1) != len(s2):
            return False
        n = len(s1)
        dp = [[[False for _ in range(n+1)] for _ in range(n)] for _ in range(n)]
        # 初始化单个字符的情况,相等为 true,不相等为 false
        for i in range(n):
            for j in range(n):
                dp[i][j][1] = s1[i] == s2[j]

        # 枚举区间长度 2~n
        for length in range(2, n+1):
            # 枚举 S 中的起点位置
            for i in range(n-length+1):
                # 枚举 T 中的起点位置
                for j in range(n-length+1):
                    # 枚举划分位置
                    for k in range(1, length):
                        # 第一种情况:S1 -> T1, S2 -> T2
                        if dp[i][j][k] and dp[i+k][j+k][length-k]:
                            dp[i][j][length] = True
                            break
                        # S1 -> T2, S2 -> T1,S1 起点 i,T2 起点 j + 前面那段长度 len-k ,S2 起点 i + 前面长度k
                        if dp[i][j+length-k][k] and dp[i+k][j][length-k]:
                            dp[i][j][length] = True
                            break


        return dp[0][0][n]

# 测试
s =Solution()
s1 = "great"
s2 = "rgeat"
print(s.isScramble(s1, s2))

# 答案:
True

合并两个有序数组

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

Java解法:

 /*
    *   三指针
    */

    public static void merge(int[] nums1, int m, int[] nums2, int n) {
    
    
        int p1 = m-1, p2 = n-1, p3 = m+n-1;
        while (p2 >= 0){
    
    
            if (p1 >= 0 && nums1[p1] > nums2[p2]){
    
    
                nums1[p3--] = nums1[p1--];
            }else {
    
    
                nums1[p3--] = nums2[p2--];
            }
        }
    }

Python解法:

# 合并两个有序数组
class Solution:
    def merge(self, nums1: list, m: int, nums2: list, n: int) -> None:
        p1, p2, p3 = m-1, n-1, n+m-1
        while p2 >= 0:
            if p1 >= 0 and nums1[p1] > nums2[p2]:
                nums1[p3] = nums1[p1]
                p1 -= 1
                p3 -= 1
            else:
                nums1[p3] = nums2[p2]
                p2 -= 1
                p3 -= 1

# 测试
s = Solution()
nums1 = [1,2,3,0,0,0]
nums2 = [2,5,6]
m, n = 3, 3
s.merge(nums1, m, nums2, n)
print(nums1)

# 答案:
[1, 2, 2, 3, 5, 6]

格雷编码

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。

给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。即使有多个不同答案,你也只需要返回其中一种。

格雷编码序列必须以 0 开头。

Java解法:

/*
    镜像规律
    n = 0, [0]
    n = 1, [0,1] 新的元素1,为0+2^0
    n = 2, [0,1,3,2]  新的元素[3,2]为[0,1]->[1,0]后分别加上2^1
    n = 3, [0,1,3,2,6,7,5,4]  新的元素[6,7,5,4]为[0,1,3,2]->[2,3,1,0]后分别加上2^2->[6,7,5,4]
     */

    public static List<Integer> grayCode(int n) {
    
    
        int shift = 1;
        List<Integer> res = new ArrayList<>();
        while (n >= 0){
    
    
            if (res.size() == 0){
    
    
                res.add(0);
            }else {
    
    
                for (int i = shift - 1 ; i >= 0 ; i--){
    
    
                    res.add(res.get((i)) + shift);
                }
                shift *= 2;
            }
            n--;
        }
        return res;
    }

Python解法

class Solution:
    def grayCode(self, n: int) -> list:
        shift = 1
        res = list()
        while n >= 0:
            if len(res) == 0:
                res.append(0)
            else:
                for i in range(shift-1, -1, -1):
                    res.append(res[i] + shift)
                shift *= 2
            n -= 1
        return res

# 测试
s = Solution()
print(s.grayCode(3))
[0, 1, 3, 2, 6, 7, 5, 4]
# 答案

子集2

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

Java解法:

public static List<List<Integer>> subsetsWithDup(int[] nums) {
    
    
        List<List<Integer>> res = new ArrayList<>();
        if (nums == null){
    
    
            return res;
        }
        int n = nums.length;
        Arrays.sort(nums);
        for (int i = 0 ; i <= n ; i++){
    
    
            backTrack(res, new ArrayList<Integer>(), nums, 0, i);
        }
        return res;
    }

    public static void backTrack(List<List<Integer>> res, List<Integer> list, int []nums, int begin, int end){
    
    
        if (list.size() == end){
    
    
            Collections.sort(list);
            if (!res.contains(list)){
    
    
                res.add(new ArrayList<>(list));
            }
            return;
        }
        for (int i = begin; i < nums.length; i++){
    
    
            list.add(nums[i]);
            backTrack(res, list, nums, i+1, end);
            list.remove(list.size()-1);
        }
    }

Python解法:

# 子集 II
# 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
# 说明:解集不能包含重复的子集。
class Solution:
    def subsetsWithDup(self, nums: list) -> list:
        if not nums or len(nums) == 0:
            return []
        n = len(nums)
        nums.sort()
        res = list()
        for i in range(n+1):
            self.backTrack(res, [], nums, 0, i)
        return res

    def backTrack(self, res: list, cur: list, nums: list, begin: int, end: int):
        if len(cur) == end:
            cur.sort()
            if cur not in res:
                res.append(cur[:])
            return

        for i in range(begin, len(nums)):
            cur.append(nums[i])
            self.backTrack(res, cur, nums, i+1, end)
            cur.remove(nums[i])



# 测试
s = Solution()
nums = [1,2,2]
print(s.subsetsWithDup(nums))

# 答案:
[[], [1], [2], [1, 2], [2, 2], [1, 2, 2]]

解码方法

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :
‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26

给你一个只含数字的 非空 字符串 num ,请计算并返回 解码 方法的 总数

Java解法:

/*
    动态规划:
        dp数组表示长度为 i 的字符可以表示的解码次数
        初始化 dp[0] = 1
        情况讨论:
            1.当一个数为0时,结果肯定是0;
            2.当长度为1时,肯定是 1 , 初始化 dp[0] = 1
            3.当字符大于等于2时的情形:
                1.当前字符为0且上一个字符为0或者当前字符为0上一个字符大于2(即30,40)这种情况下不能解码,返回0
                2.当前字符为0,那么当前字符只能与前一个字符组成组合 dp[i] = dp[i-2]
                3.当前字符不是0,但前一个字符是,这种情况下,该字符只能独立解码,dp[i] = dp[i-1];
                4.常规情况,当前字符与上一个字符的和>26(直接拼成string与26比较也可),如果大于26,那么这2字符只能一组,dp[i] = dp[i-2],否则,dp[i] = dp[i-2]+dp[i-1]

     */

    public static int numDecodings(String s) {
    
    
        if (s == null ||s.length() == 0 || s.charAt(0) == '0'){
    
    
            return 0;
        }
        char []ch = s.toCharArray();
        int n = ch.length;
        int []dp = new int[n+1];
        dp[0] = 1;
        dp[1] = ch[0] == '0' ? 0 : 1;
        for (int i = 1 ; i < n ; i++){
    
    
            if (ch[i-1] == '1' || ch[i-1] == '2' && ch[i] < '7'){
    
    
                /*如果是20、10*/
                if(ch[i] == '0') {
    
    
                    dp[i + 1] = dp[i - 1];
                /*如果是11-19、21-26*/
                }else {
    
    
                    dp[i + 1] = dp[i] + dp[i - 1];
                }
            }else if(ch[i] == '0') {
    
    
                /*如果是0、30、40、50*/
                return 0;
            }else {
    
    
                /*i-1和i无法构成一个字母*/
                dp[i + 1] = dp[i];
            }
        }
        return dp[n];
    }

Python解法:

# 解码方法
class Solution:
    def numDecodings(self, s: str) -> int:
        if not s or len(s) == 0 or s[0] == '0':
            return 0
        n = len(s)
        dp = [0 for _ in range(n+1)]
        dp[0] = 1
        dp[1] = 0 if s[0] ==' 0' else 1
        for i in range(1,n):
            if s[i-1] == '1' or s[i-1] == '2' and s[i] < '7':
                # 如果是20、10
                if s[i] == '0':
                    dp[i+1] = dp[i-1]
                # 如果是11-19、21-26
                else:
                    dp[i+1] = dp[i] + dp[i-1]
            # 如果是0、30、40、50
            elif s[i] == '0':
                return 0
            else:
                dp[i+1] = dp[i]

        return dp[n]

# 测试
s = Solution()
arr = "12"
print(s.numDecodings(arr))

# 答案:
2

反转链表 II

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

Java解法:

解法一

public static ListNode reverseBetween(ListNode head, int m, int n) {
    
    
        if (head == null){
    
    
            return head;
        }
        ListNode cur = head, pre = null;
        /*找到第一个点与它前面的点*/
        while (m > 1){
    
    
            pre = cur;
            cur = cur.next;
            m--;
            n--;
        }
        ListNode con = pre, tail = cur;
        ListNode third = null;
        /*反转第一个点与第二个点间的指针*/
        while (n > 0){
    
    
            third = cur.next;
            cur.next = pre;
            pre = cur;
            cur = third;
            n--;
        }
        if (con != null){
    
    
            con.next = pre;
        }else{
    
    
            head = pre;
        }
        tail.next = cur;
        return head;
    }

解法二

public static ListNode reverseBetween (ListNode head, int m, int n) {
    
    
        // write code here
        if(head == null) {
    
    
            return null;
        }
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy, next = dummy;
        m--; // 必须在第一个反转结点前,所以减一
        while (m-- > 0){
    
    
            pre = pre.next;
        }
        ListNode tmp1 = pre.next;
        while (n-- > 0){
    
    
            next = next.next;
        }
        ListNode tmp2 = next.next;
        next.next = null;   // 断开
        pre.next = resverNode(pre.next);
        tmp1.next = tmp2;
        return dummy.next;
    }

    public static ListNode resverNode(ListNode head){
    
    
        ListNode pre = null, cur = head;
        while (cur != null){
    
    
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }

复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式

Java解法:

static final int n = 4;
    static List<String> res = new ArrayList<>();
    static int[] segments = new int[n];

    public static List<String> restoreIpAddresses(String s) {
    
    
        segments = new int[n];
        backTrack(s, 0, 0);
        return res;
    }

    public static void backTrack(String s, int segId, int start) {
    
    
        /* 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案 */
        if (segId == n) {
    
    
            if (start == s.length()) {
    
    
                StringBuffer ipAddr = new StringBuffer();
                for (int i = 0; i < n; ++i) {
    
    
                    ipAddr.append(segments[i]);
                    if (i != n - 1) {
    
    
                        ipAddr.append('.');
                    }
                }
                res.add(ipAddr.toString());
            }
            return;
        }

        /* 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯 */
        if (start == s.length()) {
    
    
            return;
        }

        /* 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0 */
        if (s.charAt(start) == '0') {
    
    
            segments[segId] = 0;
            backTrack(s, segId + 1, start + 1);
        }

        /* 一般情况,枚举每一种可能性并递归 */
        int addr = 0;
        for (int segEnd = start; segEnd < s.length(); ++segEnd) {
    
    
            addr = addr * 10 + (s.charAt(segEnd) - '0');
            if (addr > 0 && addr <= 0xFF) {
    
    
                segments[segId] = addr;
                backTrack(s, segId + 1, segEnd + 1);
            } else {
    
    
                break;
            }
        }
    }

Python解法:

# 复原IP地址
class Solution:
    def restoreIpAddresses(self, s: str) -> list:
        n = 4
        segments = [0 for _ in range(n)]
        res = list()
        self.backTrack(s, 0, 0, n, segments, res)
        return res

    def backTrack(self, s: str, segId: int, start: int, n: int, segments: list, res: list):
        # 遍历完字符串
        if segId == n:
            if len(s) == start:
                ip = str()
                for i in range(n):
                    ip += str(segments[i])
                    if i != n-1:
                        ip += '.'
                res.append(ip)
            return

        # 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么返回
        if len(s) == start:
            return

        # 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0
        if s[start] == '0':
            segments[segId] = 0
            self.backTrack(s, segId+1, start+1, n, segments, res)

        # 一般情况,枚举每一种可能性并递归
        addr = 0
        for i in range(start, len(s)):
            addr = addr * 10 + (ord(s[i]) - ord('0'))
            if 0 < addr <= 256:
                segments[segId] = addr
                self.backTrack(s, segId+1, i+1, n, segments, res)
            else:
                break

# 测试
s = Solution()
ip = "000256"
print(s.restoreIpAddresses(ip))

# 答案:
['0.0.0.256']

二叉树的先、中、后序遍历

给定一个二叉树的根节点 root ,返回它的先、中、后遍历。

Java解法:

/* 先序遍历 */
    public static List<Integer> preorderTraversal(TreeNode root) {
    
    
        List<Integer> res = new ArrayList<>();
        prebackTrack(root, res);
        return res;
    }

    public static void prebackTrack(TreeNode root , List<Integer> res) {
    
    
        if (root == null){
    
    
            return;
        }
        res.add(root.val);
        prebackTrack(root.left, res);
        prebackTrack(root.right, res);
    }

    /* 中序遍历 */
    public List<Integer> inorderTraversal(TreeNode root) {
    
    
        List<Integer> res = new ArrayList<>();
        inorder(root, res);
        return res;
    }


    public static void inorder(TreeNode root, List<Integer> res) {
    
    
        if (root == null){
    
    
            return;
        }
        inorder(root.left, res);
        res.add(root.val);
        inorder(root.right, res);
    }

    /* 后序遍历 */
    public static List<Integer> postorderTraversal(TreeNode root) {
    
    
        List<Integer> res = new ArrayList<>();
        postbackTrack(root, res);
        return res;
    }

    public static void postbackTrack(TreeNode root , List<Integer> res) {
    
    
        if (root == null){
    
    
            return;
        }
        postbackTrack(root.left, res);
        postbackTrack(root.right, res);
        res.add(root.val);
    }

二叉树的层序遍历

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

Java解法:

/*
    * 方法一:BFS
    */
    public static List<List<Integer>> levelOrder(TreeNode root) {
    
    
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        if(root == null){
    
    
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()){
    
    
            int size = queue.size();
            List<Integer> list = new LinkedList<>();
            for (int i = 0 ; i < size ; i++){
    
    
                /* 取出结点并删除 */
                TreeNode cur = queue.peek();
                queue.poll();
                if (cur == null){
    
    
                    continue;
                }
                list.add(cur.val);
                queue.offer(cur.left);
                queue.offer(cur.right);
            }
            if (!list.isEmpty()){
    
    
                res.add(list);
            }
        }
        return res;
    }
    /*
     * 方法二:DFS
     */
    public static List<List<Integer>> levelOrder2(TreeNode root){
    
    
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        if(root != null) {
    
    
            backTrack(root,0,res);
        }
        return res;
    }

    public static void backTrack(TreeNode root , int path, List<List<Integer>> res){
    
    
        if (res.size() - 1 < path){
    
    
            res.add(new ArrayList<>(root.val));
        }
        res.get(path).add(root.val);
        if (root.left != null){
    
    
            backTrack(root.left, path+1, res);
        }
        if (root.right != null){
    
    
            backTrack(root.right, path+1, res);
        }
    }

不同的二叉搜索树

给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 。

Java解法:

/*
递归
*/
public static List<TreeNode> generateTrees(int n) {
    
    
        if (n == 0) {
    
    
            return new LinkedList<TreeNode>();
        }
        return generateTrees(1, n);
    }

    public static List<TreeNode> generateTrees(int start, int end){
    
    
        List<TreeNode> trees = new LinkedList<>();
        if (start > end){
    
    
            trees.add(null);
            return trees;
        }
        /* 枚举可行根节点 */
        for (int i = start; i <= end ; i++){
    
    
            /* 获得所有可行的左子树集合 */
            List<TreeNode> leftTrees = generateTrees(start, i-1);
            /* 得所有可行的右子树集合 */
            List<TreeNode> rightTrees = generateTrees(i+1, end);
            /* 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上 */
            for (TreeNode left : leftTrees){
    
    
                for (TreeNode right : rightTrees){
    
    
                    TreeNode curTree = new TreeNode(i);
                    curTree.left = left;
                    curTree.right = right;
                    trees.add(curTree);
                }
            }
        }
        return trees;
    }

不同的二叉搜索树2

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

Java解法:

 /*
    动态规划:
        如果整数 1 - n 中的 k 作为根节点值,则 1 - k-1 会去构建左子树,k+1 - n 会去构建右子树。
        左子树出来的形态有 a 种,右子树出来的形态有 b 种,则整个树的形态有 a * b 种。
        以 k 为根节点的 BST 种类数 = 左子树 BST 种类数 * 右子树 BST 种类数

        假设 n 个节点存在二叉排序树的个数是 G (n),令 f(i) 为以 i 为根的二叉搜索树的个数,则
        G(n) = f(1) + f(2) + f(3) + f(4) + ... + f(n)
    综合得到:
        G(n)=G(0)∗G(n−1)+G(1)∗(n−2)+...+G(n−1)∗G(0)
     */

    public static int numTrees(int n) {
    
    
        if(n < 2){
    
    
            return 1;
        }
        int []dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2 ; i <= n ; i++){
    
    
           for (int j = 1; j <= i ; j++){
    
    
               dp[i] += dp[j-1] * dp[i-j];
           }
        }
        return dp[n];
    }

Python解法:

# 不同的二叉搜索树
# 动态规划:
#         如果整数 1 - n 中的 k 作为根节点值,则 1 - k-1 会去构建左子树,k+1 - n 会去构建右子树。
#         左子树出来的形态有 a 种,右子树出来的形态有 b 种,则整个树的形态有 a * b 种。
#         以 k 为根节点的 BST 种类数 = 左子树 BST 种类数 * 右子树 BST 种类数
#
#         假设 n 个节点存在二叉排序树的个数是 G (n),令 f(i) 为以 i 为根的二叉搜索树的个数,则
#         G(n) = f(1) + f(2) + f(3) + f(4) + ... + f(n)
#     综合得到:
#         G(n)=G(0)∗G(n−1)+G(1)∗(n−2)+...+G(n−1)∗G(0)
class Solution:
    def numTrees(self, n: int) -> int:
        if n < 2:
            return 1
        dp = [0 for _ in range(n+1)]
        dp[0] = dp[1] = 1
        for i in range(2, n+1):
            for j in range(1, i+1):
                dp[i] += dp[j-1] * dp[i-j]

        return dp[n]

# 测试
s = Solution()
print(s.numTrees(3))

# 答案:
5

猜你喜欢

转载自blog.csdn.net/qq_42748009/article/details/111945165