搜索二维矩阵
编写一个高效的算法来判断 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 个数的组合。
![](/qrcode.jpg)
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