搜索相关 题目 删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。
说明: 输入可能包含了除 ( 和 ) 以外的字符。
示例 1:
1 2 输入: "()())()" 输出: ["()()()" , "(())()" ]
示例 2:
示例 3:
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 var ret []string func removeInvalidParentheses (s string ) []string { left, right := 0 , 0 for i:=0 ; i< len (s); i++ { if s[i] == '(' { left ++ } else if s[i] == ')' { if left == 0 { right ++ } else { left -- } } } ret = []string {} dfs(s, 0 , left, right) if len (ret) == 0 { ret = []string {"" } } return ret } func dfs (s string , start, left, right int ) { if left == 0 && right == 0 { if isValid(s) { ret = append (ret, s) } return } for i := start; i < len (s) ; i++ { if i > start && s[i] == s[i-1 ] { continue } if s[i] == '(' && left > 0 { dfs(s[:i]+s[i+1 :], i, left-1 , right) } else if s[i] == ')' && right > 0 { dfs(s[:i]+s[i+1 :], i, left, right-1 ) } } } func isValid (s string ) bool { left := 0 for i:=0 ; i< len (s); i++ { if s[i] == '(' { left ++ } else if s[i] == ')' { left -- if left < 0 { return false } } } return left == 0 && len (s) != 0 }
题目 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 输入: nums = [1 ,3 ,-1 ,-3 ,5 ,3 ,6 ,7 ], 和 k = 3 输出: [3 ,3 ,5 ,5 ,6 ,7 ] 解释: 滑动窗口的位置 最大值 ------ [1 3 -1 ] -3 5 3 6 7 3 1 [3 -1 -3 ] 5 3 6 7 3 1 3 [-1 -3 5 ] 3 6 7 5 1 3 -1 [-3 5 3 ] 6 7 5 1 3 -1 -3 [5 3 6 ] 7 6 1 3 -1 -3 5 [3 6 7 ] 7
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
进阶:
你能在线性时间复杂度内解决此题吗?
题解 暴力求解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func maxSlidingWindow (nums []int , k int ) []int { if len (nums) == 0 { return []int {} } ret := make ([]int , len (nums) - k +1 ) for i := k-1 ; i < len (nums); i++ { maxN := nums[i-k+1 ] for j := i-k+2 ; j <= i; j++ { if nums[j] > maxN { maxN = nums[j] } } ret[i-k+1 ] = maxN } return ret }
线性时间的话需要有一个变量保存上一次最大值和第二大值的索引,如果过了最大值则将新加进来的元素跟第二大的值进行比较,并且需要重新刷新最大值和第二大值
题目 给定一个未排序的整数数组,找出最长连续序列的长度。
要求算法的时间复杂度为 O(n)。
示例:
1 2 3 输入: [100 , 4 , 200 , 1 , 3 , 2 ] 输出: 4 解释: 最长连续序列是 [1 , 2 , 3 , 4 ]。它的长度为 4 。
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func longestConsecutive (nums []int ) int { set := make (map [int ]struct {}) for _,v := range nums { set[v]=struct {}{} } max := 0 for _,v := range nums { if _, ok := set[v]; ok { l := 1 for cur := v+1 ; ; cur++ { if _, ok := set[cur]; !ok { break } l++ delete (set, cur) } for cur := v-1 ; ; cur-- { if _, ok := set[cur]; !ok { break } l++ delete (set, cur) } delete (set, v) if l > max { max = l } } } return max }
题目 段式回文 其实与 一般回文 类似,只不过是最小的单位是 一段字符 而不是 单个字母。
举个例子,对于一般回文 “abcba” 是回文,而 “volvo” 不是,但如果我们把 “volvo” 分为 “vo”、”l”、”vo” 三段,则可以认为 “(vo)(l)(vo)” 是段式回文(分为 3 段)。
给你一个字符串 text,在确保它满足段式回文的前提下,请你返回 段 的 最大数量 k。
如果段的最大数量为 k,那么存在满足以下条件的 a_1, a_2, ..., a_k
:
示例 1:
1 2 3 输入:text = "ghiabcdefhelloadamhelloabcdefghi" 输出:7 解释:我们可以把字符串拆分成 "(ghi)(abcdef)(hello)(adam)(hello)(abcdef)(ghi)" 。
示例 2:
1 2 3 输入:text = "merchant" 输出:1 解释:我们可以把字符串拆分成 "(merchant)" 。
示例 3:
1 2 3 输入:text = 输出:11 解释:我们可以把字符串拆分成 。
示例 4:
1 2 3 输入:text = 输出:3 解释:我们可以把字符串拆分成 。
提示:
text 仅由小写英文字符组成。
1 <= text.length <= 1000
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 func longestDecomposition (text string ) int { if len (text) == 0 { return 0 } for last := len (text)-1 ; len (text) > 1 && last >= len (text)/2 ; last -- { if text[:len (text)-last] == text[last:] { return 2 + longestDecomposition(text[len (text)-last:last]) } } return 1 }
题目 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
示例:
1 2 输入: S = "ADOBECODEBANC" , T = "ABC" 输出: "BANC"
说明:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
题解 使用两个指针来进行搜索,从前往后慢慢搜索满足要求的子串,后面的指针用来找到满足要求的子串,前面的指针用来移动到长度最小。注意这里没有用字符比较的方式进行匹配,而是通过一个计数器来判断,具体请看代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func minWindow (s string , t string ) string { tMap, sMap := make ([]int , 58 ), make ([]int , 58 ) for _, i := range t { tMap[i-'A' ]++ } head, tail, minL, minR := 0 , 0 , 0 , len (s)+1 for fulfill := 0 ; tail < len (s); tail++ { if tMap[s[tail]-'A' ] == 0 { continue } sMap[s[tail]-'A' ]++ if sMap[s[tail]-'A' ] <= tMap[s[tail]-'A' ] { fulfill++ } if fulfill == len (t) { for ;tMap[s[head]-'A' ] == 0 || sMap[s[head]-'A' ] > tMap[s[head]-'A' ]; head++ { if tMap[s[head]-'A' ] >= 0 { sMap[s[head]-'A' ]-- } } if tail-head < minR-minL { minR, minL = tail,head } sMap[s[head]-'A' ]-- head++ fulfill-- } } if minR== len (s)+1 { return "" } return s[minL:minR+1 ] }
题目 给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
示例 1:
1 2 3 4 5 6 7 输入: [1 ,2 ,3 ] 1 / \ 2 3 输出: 6
示例 2:
1 2 3 4 5 6 7 8 9 输入: [-10 ,9 ,20 ,null ,null ,15 ,7 ] -10 / \ 9 20 / \ 15 7 输出: 42
题解 主要思路是递归。最开始一直没有想明白的是每次递归的函数里面应该返回什么?是这个问题要求解的最大路径和吗?其实不是。可以思考一下,对于每一个root而言,其最大路径和需要考虑的是root节点、左节点的一条边路径和右节点的一条边路径,因此这个递归函数要返回的是这个root节点的最大路径的一条边 ,这样才能递归下去。但是这样问题也来了:怎么计算和递归最大路径和呢?这里使用指针将我们要求解的值传到递归的函数里面去 ,一旦有更大的路径和就更新这个值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 func maxPathSum (root *TreeNode) int { max := -1 <<31 subPathMax(root, &max) return max } func subPathMax (root *TreeNode, max *int ) int { if root == nil { return 0 } left := Max(subPathMax(root.Left, max), 0 ) right := Max(subPathMax(root.Right, max), 0 ) *max = Max(*max, root.Val+left+right) return root.Val+Max(left, right) } func Max (i, j int ) int { if i > j { return i } return j }
题目 在二维网格 grid 上,有 4 种类型的方格:
1 表示起始方格。且只有一个起始方格。
2 表示结束方格,且只有一个结束方格。
0 表示我们可以走过的空方格。
-1 表示我们无法跨越的障碍。
返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目,每一个无障碍方格都要通过一次。
示例 1:
1 2 3 4 5 6 输入:[[1 ,0 ,0 ,0 ],[0 ,0 ,0 ,0 ],[0 ,0 ,2 ,-1 ]] 输出:2 解释:我们有以下两条路径: 1. (0 ,0 ),(0 ,1 ),(0 ,2 ),(0 ,3 ),(1 ,3 ),(1 ,2 ),(1 ,1 ),(1 ,0 ),(2 ,0 ),(2 ,1 ),(2 ,2 )2. (0 ,0 ),(1 ,0 ),(2 ,0 ),(2 ,1 ),(1 ,1 ),(0 ,1 ),(0 ,2 ),(0 ,3 ),(1 ,3 ),(1 ,2 ),(2 ,2 )
示例 2:
1 2 3 4 5 6 7 8 输入:[[1 ,0 ,0 ,0 ],[0 ,0 ,0 ,0 ],[0 ,0 ,0 ,2 ]] 输出:4 解释:我们有以下四条路径: 1. (0 ,0 ),(0 ,1 ),(0 ,2 ),(0 ,3 ),(1 ,3 ),(1 ,2 ),(1 ,1 ),(1 ,0 ),(2 ,0 ),(2 ,1 ),(2 ,2 ),(2 ,3 )2. (0 ,0 ),(0 ,1 ),(1 ,1 ),(1 ,0 ),(2 ,0 ),(2 ,1 ),(2 ,2 ),(1 ,2 ),(0 ,2 ),(0 ,3 ),(1 ,3 ),(2 ,3 )3. (0 ,0 ),(1 ,0 ),(2 ,0 ),(2 ,1 ),(2 ,2 ),(1 ,2 ),(1 ,1 ),(0 ,1 ),(0 ,2 ),(0 ,3 ),(1 ,3 ),(2 ,3 )4. (0 ,0 ),(1 ,0 ),(2 ,0 ),(2 ,1 ),(1 ,1 ),(0 ,1 ),(0 ,2 ),(0 ,3 ),(1 ,3 ),(1 ,2 ),(2 ,2 ),(2 ,3 )
示例 3:
1 2 3 4 5 输入:[[0 ,1 ],[2 ,0 ]] 输出:0 解释: 没有一条路能完全穿过每一个空的方格一次。 请注意,起始和结束方格可以位于网格中的任意位置。
提示:
1 <= grid.length * grid[0].length <= 20
题解 还算是比较简单的dfs,注意题目中说到的几个满足条件:
因此在dfs的时候要有地方可以记录已经访问过的空格个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 func uniquePathsIII (grid [][]int ) int { start, end, m, n, emp := 0 , 0 , len (grid), len (grid[0 ]), 0 isVisited := make ([]bool , m*n) for i := 0 ; i < m; i++ { for j := 0 ; j < n; j++ { if grid[i][j] == 1 { start = i*n+j } else if grid[i][j] == 2 { end = i*n+j } else if grid[i][j] == 0 { emp++ } else { isVisited[i*n+j] = true } } } num := 0 isVisited[start] = true dfs(grid, start, end, emp+1 , isVisited, &num) return num } func dfs (grid [][]int , start, end, emp int , isVisited []bool , num *int ) { if start == end { if emp <= 0 { *num ++ } return } nexts := [][]int {{-1 , 0 }, {1 , 0 }, {0 , -1 }, {0 , 1 }} i, j := start/len (grid[0 ]), start%len (grid[0 ]) for _, n := range nexts { nStart := (i + n[0 ])*len (grid[0 ]) + j + n[1 ] if i + n[0 ] < 0 || i + n[0 ] >= len (grid) || j + n[1 ] < 0 || j + n[1 ] >= len (grid[0 ]) || isVisited[nStart] { continue } isVisited[nStart] = true dfs(grid, nStart, end, emp-1 , isVisited, num) isVisited[nStart] = false } }
题目 在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1~5 来表示, 以及一块空缺用 0 来表示.
一次移动定义为选择 0 与一个相邻的数字(上下左右)进行交换.
最终当板 board 的结果是 [[1,2,3],[4,5,0]] 谜板被解开。
给出一个谜板的初始状态,返回最少可以通过多少次移动解开谜板,如果不能解开谜板,则返回 -1 。
示例:
1 2 3 输入:board = [[1 ,2 ,3 ],[4 ,0 ,5 ]] 输出:1 解释:交换 0 和 5 ,1 步完成
1 2 3 输入:board = [[1 ,2 ,3 ],[5 ,4 ,0 ]] 输出:-1 解释:没有办法完成谜板
1 2 3 4 5 6 7 8 9 10 11 输入:board = [[4 ,1 ,2 ],[5 ,0 ,3 ]] 输出:5 解释: 最少完成谜板的最少移动次数是 5 , 一种移动路径: 尚未移动: [[4 ,1 ,2 ],[5 ,0 ,3 ]] 移动 1 次: [[4 ,1 ,2 ],[0 ,5 ,3 ]] 移动 2 次: [[0 ,1 ,2 ],[4 ,5 ,3 ]] 移动 3 次: [[1 ,0 ,2 ],[4 ,5 ,3 ]] 移动 4 次: [[1 ,2 ,0 ],[4 ,5 ,3 ]] 移动 5 次: [[1 ,2 ,3 ],[4 ,5 ,0 ]]
1 2 输入:board = [[3 ,2 ,4 ],[1 ,5 ,0 ]] 输出:14
提示:
board
是一个如上所述的 2 x 3 的数组.
board[i][j]
是一个 [0, 1, 2, 3, 4, 5] 的排列.
题解 典型的bfs,按层搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import "strconv" func slidingPuzzle (board [][]int ) int { queue, set := []string {}, map [string ]int {} b := "" for i:=0 ; i<2 ; i++ { for j:=0 ; j<3 ; j++ { b += strconv.Itoa(board[i][j]) if board[i][j] == 0 { b = strconv.Itoa(i*3 +j) + b } } } queue = append (queue, b) set[b[1 :]] = 0 next := [][]int {{3 , 1 }, {4 , 0 , 2 }, {5 ,1 }, {0 , 4 }, {1 ,3 ,5 }, {2 , 4 }} for len (queue) > 0 { ind := int (queue[0 ][0 ] - '0' ) cur := queue[0 ][1 :] if cur == "123450" { return set[cur] } queue = queue[1 :] for _, n := range next[ind] { i, j := ind, n if i > j { i, j = j, i } nString := cur[:i] + cur[j:j+1 ] + cur[i+1 :j] + cur[i:i+1 ] + cur[j+1 :] if _, ok := set[nString]; ok { continue } set[nString] = set[cur] + 1 queue = append (queue, strconv.Itoa(n)+nString) } } return -1 }
排序相关 题目 假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。
注意: 总人数少于1100人。
示例
1 2 3 4 5 输入: [[7 ,0 ], [4 ,4 ], [7 ,1 ], [5 ,0 ], [6 ,1 ], [5 ,2 ]] 输出: [[5 ,0 ], [7 ,0 ], [5 ,2 ], [6 ,1 ], [4 ,4 ], [7 ,1 ]]
题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import "sort" type People [][]int func (p People) Len () int { return len (p) }func (p People) Less (i, j int ) bool { return p[i][0 ] > p[j][0 ] || ( p[i][0 ] == p[j][0 ] && p[i][1 ] < p[j][1 ]) }func (p People) Swap (i, j int ) { p[i][0 ], p[i][1 ], p[j][0 ], p[j][1 ] = p[j][0 ], p[j][1 ], p[i][0 ], p[i][1 ] }func reconstructQueue (people [][]int ) [][]int { sort.Sort(People(people)) var ret [][]int for _, p := range people { if len (ret) <= p[1 ] { ret = append (ret, p) } else { tmp := append ([][]int {}, ret[p[1 ]:]...) ret = append (ret[:p[1 ]], p) ret = append (ret, tmp...) } } return ret }
题目 给出了一个由小写字母组成的字符串 S。然后,我们可以进行任意次数的移动。
在每次移动中,我们选择前 K 个字母中的一个(从左侧开始),将其从原位置移除,并放置在字符串的末尾。
返回我们在任意次数的移动之后可以拥有的按字典顺序排列的最小字符串。
示例 1:
1 2 3 4 5 输入:S = "cba" , K = 1 输出:"acb" 解释: 在第一步中,我们将第一个字符(“c”)移动到最后,获得字符串 “bac”。 在第二步中,我们将第一个字符(“b”)移动到最后,获得最终结果 “acb”。
示例 2:
1 2 3 4 5 输入:S = "baaca" , K = 3 输出:"aaabc" 解释: 在第一步中,我们将第一个字符(“b”)移动到最后,获得字符串 “aacab”。 在第二步中,我们将第三个字符(“c”)移动到最后,获得最终结果 “aaabc”。
提示:
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import ( "strings" "sort" ) type String []byte func (s String) Len () int {return len (s)}func (s String) Less (i, j int ) bool {return s[i] < s[j]}func (s String) Swap (i, j int ) {s[i], s[j] = s[j], s[i]}func orderlyQueue (S string , K int ) string { ret := S if K == 1 { for i:=1 ; i < len (S); i++ { if strings.Compare(S[i:]+S[:i], ret) < 0 { ret = S[i:]+S[:i] } } } else { ss := String(S) sort.Sort(ss) ret = string (ss) } return ret }
题目 合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
1 2 3 4 5 6 7 输入: [ 1 ->4 ->5 , 1 ->3 ->4 , 2 ->6 ] 输出: 1 ->1 ->2 ->3 ->4 ->4 ->5 ->6
题解 这个题目首先可以使用暴力的方法求解,也就是每次遍历每个链表,取其中最小的那个,只不过这样到后面很多链表都取完了,一个循环里面每个链表还是会遍历一遍,浪费时间。更好的方式是通过归并排序,也就是二分的思路,将n个链表分成二叉树两两归并。
暴力解法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func mergeKLists (lists []*ListNode) *ListNode { minVal, index := 65535 , 0 for i, list := range lists { if list != nil && list.Val < minVal { minVal = list.Val index = i } } if minVal == 65535 && index == 0 { return nil } head := lists[index] lists[index] = lists[index].Next head.Next = mergeKLists(lists) return head }
归并排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func mergeKLists (lists []*ListNode) *ListNode { n := len (lists) if n > 2 { return mergeKLists([]*ListNode{mergeKLists(lists[:n/2 ]), mergeKLists(lists[n/2 :])}) } else if n == 1 { return lists[0 ] } else if n == 2 { if lists[0 ] == nil { return lists[1 ] } if lists[1 ] == nil { return lists[0 ] } a, b := lists[0 ], lists[1 ] if a.Val > b.Val { a, b = lists[1 ], lists[0 ] } cur := a for ; cur.Next != nil ; cur = cur.Next { if cur.Next.Val > b.Val { cur.Next, b = b, cur.Next } } cur.Next = b return a } return nil }
动态规划相关 题目 给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符 删除一个字符 替换一个字符 示例 1:
1 2 3 4 5 6 输入: word1 = "horse" , word2 = "ros" 输出: 3 解释: horse -> rorse (将 'h' 替换为 'r' )rorse -> rose (删除 'r' )rose -> ros (删除 'e' )
示例 2:
1 2 3 4 5 6 7 8 输入: word1 = "intention" , word2 = "execution" 输出: 5 解释: intention -> inention (删除 't' )inention -> enention (将 'i' 替换为 'e' )enention -> exention (将 'n' 替换为 'x' )exention -> exection (将 'n' 替换为 'c' )exection -> execution (插入 'u' )
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func minDistance (word1 string , word2 string ) int { var dp [][]int for i:=0 ; i < len (word1)+1 ; i++ { sli := make ([]int , len (word2)+1 ) dp = append (dp, sli) dp[i][0 ] = i } for j:=0 ; j <= len (word2); j++ { dp[0 ][j] = j } for i:=1 ; i < len (word1)+1 ; i++ { for j:=1 ; j < len (word2)+1 ; j++{ if word1[i-1 ] == word2[j-1 ] { dp[i][j] = dp[i-1 ][j-1 ] } else { dp[i][j] = min(min(dp[i][j-1 ], dp[i-1 ][j]), dp[i-1 ][j-1 ]) +1 } } } return dp[len (word1)][len (word2)] } func min (i, j int ) int { if i > j { return j } return i }
题目 给定由 N 个小写字母字符串组成的数组 A,其中每个字符串长度相等。
选取一个删除索引序列,对于 A 中的每个字符串,删除对应每个索引处的字符。
比如,有 A = ["babca","bbazb"]
,删除索引序列 {0, 1, 4}
,删除后 A
为["bc","az"]
。
假设,我们选择了一组删除索引 D,那么在执行删除操作之后,最终得到的数组的行中的每个元素都是按字典序排列的。
清楚起见,A[0]
是按字典序排列的(即,A[0][0] <= A[0][1] <= ... <= A[0][A[0].length - 1]
),A[1] 是按字典序排列的(即,A[1][0] <= A[1][1] <= ... <= A[1][A[1].length - 1]
),依此类推。
请你返回 D.length
的最小可能值。
示例 1:
1 2 3 4 5 6 输入:["babca" ,"bbazb" ] 输出:3 解释: 删除 0 、1 和 4 这三列后,最终得到的数组是 A = ["bc" , "az" ]。 这两行是分别按字典序排列的(即,A[0 ][0 ] <= A[0 ][1 ] 且 A[1 ][0 ] <= A[1 ][1 ])。 注意,A[0 ] > A[1 ] —— 数组 A 不一定是按字典序排列的。
示例 2:
1 2 3 输入:["edcba" ] 输出:4 解释:如果删除的列少于 4 列,则剩下的行都不会按字典序排列。
示例 3:
1 2 3 输入:["ghi" ,"def" ,"abc" ] 输出:0 解释:所有行都已按字典序排列。
提示:
1 <= A.length <= 100
1 <= A[i].length <= 100
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 func minDeletionSize (A []string ) int { d := make ([]int , len (A[0 ])) d[0 ] = 1 for i:=1 ; i<len (A[0 ]); i++ { d[i] = 1 for j := 0 ; j < i; j++ { if cmp(A, j, i) && d[j] + 1 > d[i] { d[i] = d[j] + 1 } } } max := 0 for _,v := range d { if v > max { max = v } } return len (A[0 ]) - max } func cmp (A []string , j, i int ) bool { for _, v := range A { if v[i] < v[j] { return false } } return true }
题目 有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。
求所能获得硬币的最大数量。
说明:
1 2 3 4 输入: [3 ,1 ,5 ,8 ] 输出: 167 解释: nums = [3 ,1 ,5 ,8 ] --> [3 ,5 ,8 ] --> [3 ,8 ] --> [8 ] --> [] coins = 3 *1 *5 + 3 *5 *8 + 1 *3 *8 + 1 *8 *1 = 167
题解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func maxCoins (nums []int ) int { le := len (nums) + 2 nu := make ([]int , le) nu[0 ], nu[le-1 ] = 1 , 1 for i:=1 ; i<le-1 ; i++{ nu[i] = nums[i-1 ] } var dp [][]int for i:=0 ; i < le; i++ { sl := make ([]int , le) dp = append (dp, sl) } for i := 2 ; i < le; i ++ { for left := 0 ; left < le - i; left++ { right := left + i for k := left+1 ; k < right; k++ { value := dp[left][k] + dp[k][right] + nu[left]*nu[k]*nu[right] if value > dp[left][right] { dp[left][right] = value } } } } return dp[0 ][le-1 ] }
题目 给出一些不同颜色的盒子,盒子的颜色由数字表示,即不同的数字表示不同的颜色。 你将经过若干轮操作去去掉盒子,直到所有的盒子都去掉为止。每一轮你可以移除具有相同颜色的连续 k 个盒子(k >= 1),这样一轮之后你将得到 k*k 个积分。 当你将所有盒子都去掉之后,求你能获得的最大积分和。
示例 1: 输入:
1 [1 , 3 , 2 , 2 , 2 , 3 , 4 , 3 , 1 ]
输出:
解释:
1 2 3 4 5 [1 , 3 , 2 , 2 , 2 , 3 , 4 , 3 , 1 ] ----> [1 , 3 , 3 , 4 , 3 , 1 ] (3 *3 =9 分) ----> [1 , 3 , 3 , 3 , 1 ] (1 *1 =1 分) ----> [1 , 1 ] (3 *3 =9 分) ----> [] (2 *2 =4 分)
提示:盒子的总数 n 不会超过 100。
题解 解法一:暴力求解(dfs),会超时,时间复杂度为O(n!),使用[3,8,8,5,5,3,9,2,4,4,6,5,8,4,8,6,9,6,2,8,6,4,1,9,5,3,10,5,3,3,9,8,8,6,5,3,7,4,9,6,3,9,4,3,5,10,7,6,10,7]这个长度为50的用例在桌面云上跑了一下午都没跑完,50!=3e64,这是因为有太多重复的计算,特别是算到后面newBox只有几个元素的时候很多情况会重复,在这个基础上可以通过map来存储已经算过的数组,但是会消耗较多空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func removeBoxes (boxes []int ) int { max := 0 for i:=0 ; i<len (boxes); i++ { if i>0 && boxes[i] == boxes[i-1 ] { continue } k := i+1 for ; k<len (boxes) && boxes[k] == boxes[i]; k++ {} newBox := append ([]int {}, boxes[:i]...) newBox = append (newBox, boxes[k:]...) if point := (k-i)*(k-i) + removeBoxes(newBox); point>max { max = point } } return max }
解法二:动态规划
这个题目的dp转移方程理解起来比较困难,我们先给出结果,以题目中的示例为例
转移方程的定义
对于[1, 3, 2, 2, 2, 3, 4, 3, 1]
这个数组boxes
,我们假设dp[i][j][k]
表示的是boxes[i:j]
这个子序列的最大积分,而k表示j右侧有k个与之相邻 的boxes[j],那么我们最终要求的解就是dp[0][n-1][0]
,其中n是boxes的长度。
转移方程的状态表述
为了求解dp[i][j][k]
,我们需要根据boxes[i:j]
中的情况和j后面的与boxes[j]
相同的相邻 盒子个数。首先暂且不管k是怎么算出来的,我们先看boxes[i:j]
中的情况:
假如boxes[i:j-1]
中有跟boxes[j]
相同的盒子boxes[m]
,那么我们认为可以先去掉boxes[m+1:j-1]
(即计算dp[m+1][j-1][0]
,之所以k是0,是因为这一段不考虑j-1后面是否有跟j-1重复的,注意boxes[m+1:j-1]
中可能还会跟boxes[j]
相同的盒子,我们都会遍历到),然后再计算dp[i][m][k+1]
获得积分,这样dp[i][j][k]=dp[i][m][k+1]+dp[m+1][j-1][0]
,当前我们也可以直接先去掉右边所有与boxes[j]
相同的相邻 盒子并得到积分(k+1)*(k+1)
,这个跟下面的情况相同
假如boxes[i:j-1]
中没有有跟boxes[j]
相同的盒子,那么我们可以直接去掉box[j]
和右边所有与boxes[j]
相同的相邻 盒子并得到积分(k+1)*(k+1)
,这样dp[i][j][k]=dp[i][j-1][0]+(k+1)*(k+1)
,这里之所以dp[i][j-1][0]
中k=0是因为j-1后面已经没有其他盒子了
总结下来dp[i][j][k]
总共有两种情况:
dp[i][j][k]=max{dp[i][m][k+1]+dp[m+1][j-1][0], dp[i][j-1][0]+(k+1)*(k+1)}
针对这个方程,我们可以使用题目中的示例演算一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 func removeBoxes (boxes []int ) int { dp := [][][]int {} for i:=0 ; i< len (boxes); i++ { dp1 := [][]int {} for j:=0 ; j< len (boxes); j++ { dp1 = append (dp1, make ([]int , len (boxes))) } dp = append (dp, dp1) } return maxPoint(0 , len (boxes)-1 , 0 , dp, boxes) } func maxPoint (i, j, k int , dp [][][]int , boxes []int ) int { if dp[i][j][k] > 0 || i > j { return dp[i][j][k] } if i == j { dp[i][j][k] = (k+1 )*(k+1 ) return dp[i][j][k] } max := maxPoint(i, j-1 , 0 , dp, boxes) + (k+1 )*(k+1 ) for m:=i; m<j; m++ { if boxes[m] != boxes[j] { continue } if cur := maxPoint(i, m, k+1 , dp, boxes) + maxPoint(m+1 , j-1 , 0 , dp, boxes); cur > max { max = cur } } dp[i][j][k] = max return max }
题目 一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
-2 (K)
-3
3
-5
-10
1
10
30
-5 (P)
说明:
骑士的健康点数没有上限。
任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
题解 这道题目如果通过正向的思维(从骑士的位置走到公主的位置)来暴力求解会产生一定的问题:
骑士在[i,j]
位置时可以选择从[i-1,j]
或者[i,j-1]
过来,在选择时应该以什么策略进行判断?d[i][j]
里面是不是还需要记录初始最小血量和当前的血量?
这里既需要考虑初始血量最小,又需要考虑有些房间是可以加血的,那么在选择下一步的时候到底应该选初始血量小的还是走过去之后当前血量更大的?
以上问题其实没有办法判断,无法使用贪心的方式来决策。那么我们可以换一个思路,从后往前看:假如骑士从[i,j]
这个位置出发到达终点那么最少需要多少初始血量:
我们可以从后往前看,我们假设d[i][j]
表示的是骑士从[i,j]
这个位置出发所需要的初始血量,当d[i][j]
是负数时表示初始需要血量,且后面缺少的血量为1-d[i][j]
,d[i][j]
是0则表示初始不需要血
我们知道骑士的下一步只能走右边或者下面,因此d[i][j]
只与d[i][j+1]
(右)和d[i+1][j]
(下)有关,因此我们要选的是d[i][j+1]
和d[i+1][j]
中血量最大的那个,这样对于d[i][j]
是最有利的,因此转移方程就变成了d[i][j]= max{d[i][j+1], d[i+1][j]} + l[i][j]
,如果算下来d[i][j]>0
,那么就说明骑士从[i,j]
这个位置出发到达终点是不需要血量的,也就是d[i][j]=0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func calculateMinimumHP (dungeon [][]int ) int { n, m := len (dungeon), len (dungeon[0 ]) dp:=make ([]int , m) dp[m-1 ]=0 for i:=0 ;i<m-1 ;i++{ dp[i]=-65535 } for i:=n-1 ;i>=0 ;i--{ dp[m-1 ]+=dungeon[i][m-1 ] if dp[m-1 ]>0 { dp[m-1 ]=0 } for j:=m-2 ;j>=0 ;j--{ if dp[j+1 ]>dp[j] { dp[j]=dp[j+1 ] } dp[j]+=dungeon[i][j] if dp[j]>0 { dp[j]=0 } } } return 1 -dp[0 ] }
图论相关 题目 在一个 N x N 的坐标方格 grid 中,每一个方格的值 grid[i][j] 表示在位置 (i,j) 的平台高度。
现在开始下雨了。当时间为 t 时,此时雨水导致水池中任意位置的水位为 t 。你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。当然,在你游泳的时候你必须待在坐标方格里面。
你从坐标方格的左上平台 (0,0) 出发。最少耗时多久你才能到达坐标方格的右下平台 (N-1, N-1)?
示例 1:
输入: [[0,2],[1,3]] 输出: 3 解释: 时间为0时,你位于坐标方格的位置为 (0, 0)。 此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。
等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置
示例2:
输入: [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]] 输入: 16 解释: 0 1 2 3 4 24 23 22 21 5 12 13 14 15 16 11 17 18 19 2010 9 8 7 6
最终的路线用加粗进行了标记。 我们必须等到时间为 16,此时才能保证平台 (0, 0) 和 (4, 4) 是连通的
提示:
2 <= N <= 50
.
grid[i][j]
位于区间 [0, ..., N*N - 1]
内。
题解 本题实际上是一个变形的最短路径问题,使用dijkstra就可以实现了,注意dijkstra算法的基本思路:
计算新加入到最短路径中的节点k相邻的所有未加入的节点到起始点的距离,如果经过节点k到达这个节点的距离更短,则更新这个节点的最小距离
遍历所有未加入最短路径的节点,找到距离起始节点最近的那个节点并加入到最短路径中,然后重复上一步
注意上述循环可以反过来,只要控制好循环之前的初始化就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 func swimInWater (grid [][]int ) int { n, m := len (grid), len (grid[0 ]) dist := [][]int {} isVisited := [][]bool {} for i:=0 ; i<n; i++{ dist = append (dist, make ([]int , m)) isVisited = append (isVisited, make ([]bool , m)) for j:=0 ; j < m; j++ { dist[i][j] = 65535 } } dist[0 ][0 ] = grid[0 ][0 ] isVisited[0 ][0 ]=true curi, curj := 0 , 0 for curi!=n-1 || curj!=m-1 { arounds := [][]int {{0 ,-1 },{0 ,1 },{-1 ,0 },{1 ,0 }} for _, around := range arounds { nexti, nextj := curi+around[0 ], curj+around[1 ] if nexti<n && nexti>=0 && nextj<m && nextj>=0 && !isVisited[nexti][nextj] { dist[nexti][nextj]=dist[curi][curj] if grid[nexti][nextj] > dist[curi][curj] { dist[nexti][nextj]=grid[nexti][nextj] } } } minDist := 65535 for i:=0 ; i<n; i++{ for j:=0 ; j < m; j++ { if !isVisited[i][j] && dist[i][j] < minDist { minDist, curi, curj = dist[i][j], i, j } } } isVisited[curi][curj]=true } return dist[n-1 ][m-1 ] }