mirror of
https://gitlab.com/game-loader/hugo.git
synced 2025-04-20 05:52:07 +08:00
leetcode update
This commit is contained in:
parent
8d163be671
commit
66cb17bc53
@ -10716,3 +10716,683 @@ func minimumPushes(word string) int {
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
## day163 2024-08-07
|
||||
### 273. Integer to English words
|
||||
Convert a non-negative integer num to its English words representation.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
本题虽然是一道难题, 但思路也是比较清晰的, 首先理解英文的计数是以千为一组进行计数的, 如千(10^3), 百万(10^6), 十亿(10^9). 则通过一个函数单独处理每个三位一组的数据中的三位数最终解析的英文, 再补充上该三位数对应的单位即可. 注意处理0这种特殊情况
|
||||
|
||||
### 代码
|
||||
```go
|
||||
func numberToWords(num int) string {
|
||||
if num == 0{
|
||||
return "Zero"
|
||||
}
|
||||
data := map[int]string{0: "Zero",
|
||||
1: "One",
|
||||
2: "Two",
|
||||
3: "Three",
|
||||
4: "Four",
|
||||
5: "Five",
|
||||
6: "Six",
|
||||
7: "Seven",
|
||||
8: "Eight",
|
||||
9: "Nine",
|
||||
10: "Ten",
|
||||
11: "Eleven",
|
||||
12: "Twelve",
|
||||
13: "Thirteen",
|
||||
14: "Fourteen",
|
||||
15: "Fifteen",
|
||||
16: "Sixteen",
|
||||
17: "Seventeen",
|
||||
18: "Eighteen",
|
||||
19: "Nineteen",
|
||||
20: "Twenty",
|
||||
30: "Thirty",
|
||||
40: "Forty",
|
||||
50: "Fifty",
|
||||
60: "Sixty",
|
||||
70: "Seventy",
|
||||
80: "Eighty",
|
||||
90: "Ninety",
|
||||
100: "Hundred",
|
||||
1000: "Thousand",
|
||||
1000000: "Million",
|
||||
1000000000: "Billion",
|
||||
}
|
||||
var result strings.Builder
|
||||
if num/1000000000 > 0{
|
||||
result.WriteString(comma(num/1000000000,data))
|
||||
result.WriteString(" ")
|
||||
result.WriteString("Billion")
|
||||
result.WriteString(" ")
|
||||
}
|
||||
if (num%1000000000)/1000000 > 0{
|
||||
result.WriteString(comma((num%1000000000)/1000000,data))
|
||||
result.WriteString(" ")
|
||||
result.WriteString("Million")
|
||||
result.WriteString(" ")
|
||||
}
|
||||
if (num%1000000)/1000 > 0{
|
||||
result.WriteString(comma((num%1000000)/1000,data))
|
||||
result.WriteString(" ")
|
||||
result.WriteString("Thousand")
|
||||
result.WriteString(" ")
|
||||
}
|
||||
if (num%1000) > 0{
|
||||
result.WriteString(comma(num%1000,data))
|
||||
}
|
||||
resultstring := result.String()
|
||||
if resultstring[len(resultstring)-1] == ' '{
|
||||
resultstring = resultstring[:len(resultstring)-1]
|
||||
}
|
||||
return resultstring
|
||||
}
|
||||
|
||||
func comma(input int,data map[int]string)string{
|
||||
var result strings.Builder
|
||||
if input / 100 > 0{
|
||||
result.WriteString(data[input/100])
|
||||
result.WriteString(" ")
|
||||
result.WriteString("Hundred")
|
||||
|
||||
}
|
||||
if input % 100 < 20 && (input % 100) != 0{
|
||||
if input / 100 > 0{
|
||||
result.WriteString(" ")
|
||||
}
|
||||
result.WriteString(data[input % 100])
|
||||
}else if (input % 100) != 0{
|
||||
if input / 100 > 0{
|
||||
result.WriteString(" ")
|
||||
}
|
||||
result.WriteString(data[input%100-input%10])
|
||||
if input % 10 != 0{
|
||||
result.WriteString(" ")
|
||||
result.WriteString(data[input%10])
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
```
|
||||
|
||||
## day164 2024-08-08
|
||||
### 885. Spiral Matrix III
|
||||
You start at the cell (rStart, cStart) of an rows x cols grid facing east. The northwest corner is at the first row and column in the grid, and the southeast corner is at the last row and column.
|
||||
|
||||
You will walk in a clockwise spiral shape to visit every position in this grid. Whenever you move outside the grid's boundary, we continue our walk outside the grid (but may return to the grid boundary later.). Eventually, we reach all rows * cols spaces of the grid.
|
||||
|
||||
Return an array of coordinates representing the positions of the grid in the order you visited them.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
本题按照题目要求进行顺时针遍历, 对于超出边界的位置只需不将其输出到结果数组中即能完成题目的要求. 进行顺时针遍历的过程为每走完两个方向就将前进的步数加1, 如一开始的几次移动分别为右1, 下1, 左2, 上2, 右3, 下3... 因此按照这个规律遍历整个矩阵并将不超出边界的位置坐标放入结果数组中. 移动过程中使用一个变量表示方向, 并记录已经走过的在矩阵内部的位置的个数, 直到走过的个数与矩阵内元素的个数相同结束遍历.
|
||||
|
||||
### 代码
|
||||
```go
|
||||
func spiralMatrixIII(rows int, cols int, rStart int, cStart int) [][]int {
|
||||
position := make([]int, 2)
|
||||
position[0] = rStart
|
||||
position[1] = cStart
|
||||
count := 0
|
||||
sum := rows*cols
|
||||
step := 1
|
||||
result := [][]int{}
|
||||
result = append(result, []int{rStart,cStart})
|
||||
// 0,1,2,3 --- right down left up
|
||||
direction := 0
|
||||
for count < sum-1{
|
||||
for i:=0;i<step;i++{
|
||||
if direction == 0{
|
||||
position[1]++
|
||||
}else if direction == 1{
|
||||
position[0]++
|
||||
}else if direction == 2{
|
||||
position[1]--
|
||||
}else if direction == 3{
|
||||
position[0]--
|
||||
}
|
||||
if position[0] >= 0 && position[0] < rows && position[1] >= 0 && position[1] < cols{
|
||||
saveposition := []int{position[0], position[1]}
|
||||
result = append(result, saveposition)
|
||||
count++
|
||||
}
|
||||
}
|
||||
direction = (direction+1) % 4
|
||||
if direction == 2 || direction == 0{
|
||||
step++
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
## day165 2024-08-09
|
||||
### 840. Magic Squares In Grid
|
||||
A 3 x 3 magic square is a 3 x 3 grid filled with distinct numbers from 1 to 9 such that each row, column, and both diagonals all have the same sum.
|
||||
|
||||
Given a row x col grid of integers, how many 3 x 3 contiguous magic square subgrids are there?
|
||||
|
||||
Note: while a magic square can only contain numbers from 1 to 9, grid may contain numbers up to 15.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
本题要求找到所有满足行列及斜的和都相同且只由1-9组成的的3\*3矩阵的个数, 则将1-9填入3\*3矩阵中且满足如上要求必须每行每列的和均为1-9的和的1/3即15, 因此只需从头开始依次判断矩阵中的每个3\*3矩阵是否满足每行每列及斜行和都为15即可, 任意条件不满足即可继续判断下一个矩阵是否满足条件. 因为矩阵的行列数都不超过10, 因此需要判断的次数并不多. 这里判断全部行列是否满足条件只需将3\*3矩阵遍历一遍即可, 用一个sums数组保存列和, 在按行遍历的同时将每列的和可以同时求出(sums[0],[1],[2]分别表示三列, 每次将对应列上的元素加到对应的列和中).
|
||||
|
||||
### 代码
|
||||
```go
|
||||
func numMagicSquaresInside(grid [][]int) int {
|
||||
rows := len(grid)
|
||||
cols := len(grid[0])
|
||||
if rows<3 || cols<3{
|
||||
return 0
|
||||
}
|
||||
result := 0
|
||||
for rowi := 0;rowi<rows-2;rowi++{
|
||||
for coli := 0;coli<cols-2;coli++{
|
||||
if judge(rowi, coli, grid){
|
||||
result++
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func judge(rowi int, coli int, grid [][]int)bool{
|
||||
visited := make([]bool, 9)
|
||||
sums := make([]int, 5)
|
||||
for startrow := rowi;startrow<rowi+3;startrow++{
|
||||
rowsum := 0
|
||||
for startcol := coli;startcol<coli+3;startcol++{
|
||||
rowsum += grid[startrow][startcol]
|
||||
sums[startcol-coli] += grid[startrow][startcol]
|
||||
if grid[startrow][startcol] >=1 && grid[startrow][startcol] <= 9{
|
||||
visited[grid[startrow][startcol]-1] = true
|
||||
}
|
||||
}
|
||||
if rowsum != 15{
|
||||
return false
|
||||
}
|
||||
}
|
||||
sums[3] = grid[rowi][coli]+grid[rowi+1][coli+1]+grid[rowi+2][coli+2]
|
||||
sums[4] = grid[rowi][coli+2]+grid[rowi+1][coli+1]+grid[rowi+2][coli]
|
||||
for _,sum := range sums{
|
||||
if sum != 15{
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _,visit := range visited{
|
||||
if !visit{
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
## day166 2024-08-10
|
||||
### 959. Regions Cut By Slashes
|
||||
An n x n grid is composed of 1 x 1 squares where each 1 x 1 square consists of a '/', '\', or blank space ' '. These characters divide the square into contiguous regions.
|
||||
|
||||
Given the grid grid represented as a string array, return the number of regions.
|
||||
|
||||
Note that backslash characters are escaped, so a '\' is represented as '\\'.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
像这样将一些小的区域连接在一起构成一个大区域, 最终计数组合成的大区域有几个的问题常常使用并查集解决, 让每个大区域派出一个代表元, 最终统计代表元的个数即可知道大区域的个数. 问题在于, 如何合并, 合并什么. 我们考虑'/',和'\'这两种情况, 可以发现如果将方块分割成四个小三角形, 右边的三角形都要和右侧方块的左边三角形合并在一起, 下面的三角形要和下面方块的上三角形合并在一起. 那么我们可以将所有的方块都分割成四个小三角形, 再根据题目中提供的斜线情况来合并这些小三角形(相当于给斜线做减法).
|
||||
|
||||
### 代码
|
||||
```go
|
||||
func regionsBySlashes(grid []string) int {
|
||||
rmap := make(map[int]int, 0)
|
||||
ll := len(grid)
|
||||
s := NewSet(4*ll*ll)
|
||||
|
||||
for i := 0; i < ll; i++ {
|
||||
|
||||
for j := 0; j < ll; j++ {
|
||||
zero := i * 4 * ll + 4 * j
|
||||
switch grid[i][j] {
|
||||
case '\\':
|
||||
s.Union(zero+1, zero+2)
|
||||
s.Union(zero+0, zero+3)
|
||||
case '/':
|
||||
s.Union(zero+0, zero+1)
|
||||
s.Union(zero+2, zero+3)
|
||||
case ' ':
|
||||
s.Union(zero+0, zero+1)
|
||||
s.Union(zero+0, zero+2)
|
||||
s.Union(zero+0, zero+3)
|
||||
}
|
||||
|
||||
if j + 1 != ll {
|
||||
s.Union(zero+2, zero+4)
|
||||
}
|
||||
if i + 1 != ll {
|
||||
s.Union(zero+3, zero+4*ll+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 4*ll*ll; i++ {
|
||||
rmap[s.Find(i)] = 0
|
||||
}
|
||||
|
||||
return len(rmap)
|
||||
}
|
||||
|
||||
type Set struct {
|
||||
s []int
|
||||
}
|
||||
|
||||
func NewSet(n int) *Set {
|
||||
s := make([]int, n)
|
||||
for i := 0; i < len(s); i++ {
|
||||
s[i] = i
|
||||
}
|
||||
return &Set {
|
||||
s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set)Union(a,b int) {
|
||||
if s.Find(a) == s.Find(b) {
|
||||
return
|
||||
} else {
|
||||
s.s[s.Find(b)] = s.s[s.Find(a)]
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set) Find(x int) int {
|
||||
if s.s[x] == x {
|
||||
return x
|
||||
} else {
|
||||
return s.Find(s.s[x])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## day167 2024-08-11
|
||||
### 1568. Minimum Number of Days to Disconnect Island
|
||||
You are given an m x n binary grid grid where 1 represents land and 0 represents water. An island is a maximal 4-directionally (horizontal or vertical) connected group of 1's.
|
||||
|
||||
The grid is said to be connected if we have exactly one island, otherwise is said disconnected.
|
||||
|
||||
In one day, we are allowed to change any single land cell (1) into a water cell (0).
|
||||
|
||||
Return the minimum number of days to disconnect the grid.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
本题是一道难题, 但整体思路还是比较清晰, 对于任意一个岛屿矩阵, 如果有岛屿之间不相连, 则直接返回0, 如果只有一个岛屿且存在一个点使得去掉这个点后这一个岛屿会被分割为两个岛屿, 则返回1, 如果不存在这样的点则直接返回2. 对于任意形状的岛屿, 将其中最多两个土地变为水就能使一个岛屿分割为两个(考虑111,111,111. 只需将任意一个角分割出来即可, 即分割后为101,110,111, 此时已经将原来的一个岛屿分割为两个). 判断是否只有一个岛屿是之前已经解决过的问题, 使用dfs即可解决. 但判断是否存在一个点使得去掉这个点后一个岛屿会被分割为两个则是一个难解决的问题, 直观上可能会想到使用并查集来解决, 这种方案是可行的, 只是时间复杂度会比较高, 因为要遍历每一个土地来判断删掉这个土地后并查集是否会变成两个. 这里要引入图论中的一些概念, 这样的点在无向图中被称为割点. 要求割点就要引入一个求强连通分量的算法Tarjan算法. 有趣的是, 这个算法的发明者也是并查集的作者Robert E. Tarjan. 该作者在图算法上有着极为杰出的贡献. 对此算法的讲解推荐下面的youtube视频
|
||||
|
||||
[tarjan算法](https://www.youtube.com/watch?v=TyWtx7q2D7Y)
|
||||
|
||||
### 代码
|
||||
```go
|
||||
func minDays(grid [][]int) int {
|
||||
|
||||
m := len(grid)
|
||||
n := len(grid[0])
|
||||
cntOne := 0
|
||||
|
||||
dirs := [][]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
|
||||
g := make(map[int][]int, m*n)
|
||||
for i := 0; i < m; i++ {
|
||||
for j := 0; j < n; j++ {
|
||||
if grid[i][j] == 1 {
|
||||
cntOne++
|
||||
for _, d := range dirs {
|
||||
nx, ny := i+d[0], j+d[1]
|
||||
if nx < 0 || nx >= m || ny < 0 || ny >= n {
|
||||
continue
|
||||
}
|
||||
if grid[nx][ny] == 1 {
|
||||
g[i*n+j] = append(g[i*n+j], nx*n+ny)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//特殊处理
|
||||
if cntOne == 1 {
|
||||
return 1
|
||||
}
|
||||
if len(g) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
isCut := make([]bool, m*n)
|
||||
dfn := make([]int, m*n)
|
||||
low := make([]int, m*n)
|
||||
|
||||
dfsClock := 0
|
||||
cnt := 0
|
||||
var tarjan func(int, int) int
|
||||
tarjan = func(v, fa int) int {
|
||||
dfsClock++
|
||||
dfn[v] = dfsClock
|
||||
low[v] = dfsClock
|
||||
childCnt := 0
|
||||
for _, w := range g[v] {
|
||||
if dfn[w] == 0 {
|
||||
childCnt++
|
||||
lowW := tarjan(w, v)
|
||||
low[v] = min(low[v], lowW)
|
||||
if lowW >= dfn[v] {
|
||||
isCut[v] = true
|
||||
cnt++
|
||||
}
|
||||
} else {
|
||||
low[v] = min(low[v], dfn[w])
|
||||
}
|
||||
}
|
||||
if fa == -1 && childCnt == 1 {
|
||||
if isCut[v] {
|
||||
cnt--
|
||||
}
|
||||
isCut[v] = false
|
||||
}
|
||||
return low[v]
|
||||
}
|
||||
|
||||
cntR := 0
|
||||
for i := range grid {
|
||||
for j, v := range grid[i] {
|
||||
if v == 1 && dfn[i*n+j] == 0 {
|
||||
if cntR > 0 {
|
||||
return 0
|
||||
}
|
||||
cntR++
|
||||
tarjan(i*n+j, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if cnt > 0 {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
}
|
||||
```
|
||||
### 总结
|
||||
图算法往往比较复杂, 在理解抽象的图算法基础上还需要我们将问题抽象出来, 对应到具体的图的问题中, 如是无向图还是有向图, 求解的是图中的什么特殊点或者特殊边. 这些都需要长期的积累.
|
||||
|
||||
[洛谷--割点](https://www.luogu.com.cn/problem/P3388)
|
||||
|
||||
## day168 2024-08-12
|
||||
### 703. Kth Largest Element in a Stream
|
||||
Design a class to find the kth largest element in a stream. Note that it is the kth largest element in the sorted order, not the kth distinct element.
|
||||
|
||||
Implement KthLargest class:
|
||||
|
||||
KthLargest(int k, int[] nums) Initializes the object with the integer k and the stream of integers nums.
|
||||
int add(int val) Appends the integer val to the stream and returns the element representing the kth largest element in the stream.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
题目很好理解, 即不断向数组中添加数字但始终返回当前数组中所有数中第K大的数字(可以重复,这里的第K大是排序后的第K个). 这里可以使用一个最小堆, 因为我们只需要第K大的数字, 其余数字并不关心, 因此只需保证最大堆中永远只有k个数字则此时堆顶数字即为当前stream中全部数字中第K大的数字, 能够使用这种方法的另一个原因在于这里的stream只有添加操作而没有删除操作. 如果有删除操作的话, 因为只保留了全部stream中到目前为止第K大及以下的数字, 如果删掉堆顶, 则无法确定之前被舍弃的数字中哪个应该重新成为堆顶(如1,2,3,4 若堆只保留三个数字, 则当到4时堆内为4,3,2三个数, 堆顶为2如果有删除操作将3删掉, 则1应该成为堆顶, 但1已经被舍弃). 因此在有删除操作的情况下这种方法并不适用.
|
||||
|
||||
### 代码
|
||||
```go
|
||||
import (
|
||||
"container/heap"
|
||||
)
|
||||
|
||||
type Kheap []int
|
||||
|
||||
func (kheap Kheap) Len() int{
|
||||
return len(kheap)
|
||||
}
|
||||
|
||||
func (kheap Kheap) Less(i ,j int) bool{
|
||||
return kheap[i] < kheap[j]
|
||||
}
|
||||
|
||||
func (kheap Kheap) Swap(i,j int){
|
||||
kheap[i], kheap[j] = kheap[j], kheap[i]
|
||||
}
|
||||
|
||||
func (kheap *Kheap) Push(num interface{}){
|
||||
*kheap = append(*kheap, num.(int))
|
||||
}
|
||||
|
||||
func (kheap *Kheap) Pop()(num interface{}){
|
||||
n := len(*kheap)
|
||||
x := (*kheap)[n-1]
|
||||
*kheap = (*kheap)[0:n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
type KthLargest struct {
|
||||
k int
|
||||
nums Kheap
|
||||
}
|
||||
|
||||
|
||||
func Constructor(k int, nums []int) KthLargest {
|
||||
kheap := Kheap{}
|
||||
if len(nums) <= k{
|
||||
for _, num := range nums{
|
||||
kheap = append(kheap, num)
|
||||
}
|
||||
heap.Init(&kheap)
|
||||
}else{
|
||||
for i:=0;i<k;i++{
|
||||
kheap = append(kheap, nums[i])
|
||||
}
|
||||
heap.Init(&kheap)
|
||||
for i:=k;i<len(nums);i++{
|
||||
if nums[i] > kheap[0]{
|
||||
heap.Pop(&kheap)
|
||||
heap.Push(&kheap, nums[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
return KthLargest{k, kheap}
|
||||
}
|
||||
|
||||
|
||||
func (this *KthLargest) Add(val int) int {
|
||||
if len(this.nums) < this.k{
|
||||
heap.Push(&(this.nums), val)
|
||||
return this.nums[0]
|
||||
}else{
|
||||
if val > this.nums[0]{
|
||||
heap.Pop(&(this.nums))
|
||||
heap.Push(&(this.nums), val)
|
||||
return this.nums[0]
|
||||
}
|
||||
}
|
||||
return this.nums[0]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Your KthLargest object will be instantiated and called as such:
|
||||
* obj := Constructor(k, nums);
|
||||
* param_1 := obj.Add(val);
|
||||
*/
|
||||
```
|
||||
|
||||
## day169 2024-08-13
|
||||
### 40. Combination Sum II
|
||||
|
||||
Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sum to target.
|
||||
|
||||
Each number in candidates may only be used once in the combination.
|
||||
|
||||
Note: The solution set must not contain duplicate combinations.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
这种题目第一反应是需要遍历所有的数字组合来找到符合条件的数字, 但显然可以根据各种条件进行剪枝, 那么总体思路就是在遍历所有组合的基础上进行剪枝操作. 首先想清楚遍历所有组合可以使用回溯算法. 其次是可以从哪些方面进行剪枝. 目标是找到所有和等于目标数字的数字组合, 则显然当当前数字和已经大于目标数字时, 就不需要再去和后面的数字进行组合了, 这是最基本的剪枝. 再次考虑题目要求, 要求找到所有的组合且组合不能重复, 那么这里我们可以对相同的数字进行剪枝, 因为如果有多个相同数字, 在数组已有数字相同的情况下(前缀一样)选取任意一个得到的后续的组合都是相同的. 举例来说, 对于目标和为3的情况, 如果我们有1,2,2,2四个数字, 则选取1和任意一个2对应的组合都是相同的. 这里的重要前提是已有数字相同, 即选择的层级在同一层, 仍然举例来说, 如果目标和为8, 且有1,2,2,2,3,5这几个数字. 则当已有数字为1时, 选取三个2中的任意一个2后, 再与2后面的数字组合, 得到的数组都是一样的, 但如果已有数字是1,2 则此时选取后面两个2中的任意一个最终得到的组合都是一样的. 可能有人会在此处困惑, 这里在1时从三个2中选一个得到的不也是1,2吗, 和在已有数字是1,2的情况有什么区别呢, 这里的关键在回溯的层级, 如果选择1位于第一层, 则接下来回溯需要我们先选择第一个2继续向下搜索, 结束后再选择第二个2继续搜索, 最后选择第三个2继续搜索, 此时这三个2都位于第二层. 而已有1,2的情况相当于已经选择了1和第一个2后的继续向下搜索的过程, 此时继续选择第二个2或者第三个2, 这两个2位于第三层. 我画了一个简单的示意图, 可以很好的理解这个问题(不同颜色表示不同的2, 将整个搜索树展开可以清楚的看到位于不同层级的2, 这些2在不同层级的作用不同). 由此可知, 对于这个问题, 位于同一层的相同数字起到的作用是一样的. 可以对其进行剪枝.
|
||||
|
||||

|
||||
|
||||
|
||||
### 代码
|
||||
```go
|
||||
func combinationSum2(candidates []int, target int) [][]int {
|
||||
sort.Ints(candidates) // Sort the array to manage duplicates
|
||||
result := [][]int{}
|
||||
current := []int{}
|
||||
|
||||
var backtrack func(target, start int)
|
||||
backtrack = func(target, start int) {
|
||||
if target == 0 { // Base case: if we hit the target, add the current combination to the result
|
||||
result = append(result, append([]int{}, current...))
|
||||
return
|
||||
}
|
||||
for i := start; i < len(candidates); i++ {
|
||||
if i > start && candidates[i] == candidates[i-1] {
|
||||
continue // 跳过同层级的相同数字
|
||||
}
|
||||
if candidates[i] > target {
|
||||
break // 单个数字已经大于目标直接退出
|
||||
}
|
||||
current = append(current, candidates[i])
|
||||
backtrack(target-candidates[i], i+1)
|
||||
current = current[:len(current)-1]
|
||||
}
|
||||
}
|
||||
|
||||
backtrack(target, 0)
|
||||
return result
|
||||
}
|
||||
```
|
||||
### 总结
|
||||
看评论区有人提到这个问题与子集和问题相同, 子集和是个经典的np完全问题. 因此这个问题只能遍历所有组合找到所有符合的答案, 剪枝不过是局部优化, 在最坏情况下可能所有组合都满足条件, 此时相当于不存在剪枝.
|
||||
|
||||
## day170 2024-08-14
|
||||
### 719. Find K-th Smallest Pair Distance
|
||||
The distance of a pair of integers a and b is defined as the absolute difference between a and b.
|
||||
|
||||
Given an integer array nums and an integer k, return the kth smallest distance among all the pairs nums[i] and nums[j] where 0 <= i < j < nums.length.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
本题是一道难题, 因为要找的是数对差中第k小的数对, 因此重要的是数字与数字的差, 暴力方法即为将所有的数对差都求出来后排序即可直接得到第k位的差值. 但显然可以对此进行剪枝, 这时可以想到如果数组有序的话, 当数字a和数字b的差已经大于目标值时, 就没必要继续计算a和大于b的数字的差值. 问题在于最终要求的就是这里的目标值, 在没有目标值的情况下, 如何确定目标值, 这个问题就变成了一个查找问题. 在数组排序后, 我们可以知道整个数组中两数差的最大值(数组第一个数字和最后一个数字的差), 有了最大值和最小值(0), 就可以使用二分法来假设目标值, 假设好目标值后计算小于等于目标值的差值个数, 如果大于等于k个则更新右边界, 否则更新左边界. 这里要注意为什么等于k的时候还要继续更新右边界, 因为通过二分法假设目标值可能会出现目标值并不存在于数组的差值中但仍然满足第k个这个条件, 如差值为1,2,3,5. 而假设当前的假设目标值为4, 则小于4和小于5的差值都为3个, 但4其实并不存在数组的差值中, 要继续更新边界直到找到准确的在数组差值中的目标值.
|
||||
|
||||
|
||||
### 代码
|
||||
```go
|
||||
func smallestDistancePair(nums []int, k int) int {
|
||||
var enough = func(x int) bool {
|
||||
var i, j, count int
|
||||
j++
|
||||
for i < len(nums) {
|
||||
for j < len(nums) && nums[j] - nums[i] <= x {
|
||||
j++
|
||||
}
|
||||
count += j - i - 1
|
||||
i++
|
||||
}
|
||||
return count >= k
|
||||
}
|
||||
|
||||
sort.Ints(nums)
|
||||
var lo, hi int
|
||||
hi = nums[len(nums) - 1] - nums[0]
|
||||
|
||||
for lo < hi {
|
||||
cur := lo + (hi - lo) / 2
|
||||
if !enough(cur) {
|
||||
lo = cur + 1
|
||||
} else {
|
||||
hi = cur
|
||||
}
|
||||
|
||||
}
|
||||
return lo
|
||||
}
|
||||
```
|
||||
### 总结
|
||||
像二分法这种为人熟知的方法一定要深刻理解其背后的思想和目标场景, 才能在复杂的背景下准确应用.
|
||||
|
||||
## day171 2024-08-15
|
||||
### 860. Lemonade Change
|
||||
At a lemonade stand, each lemonade costs $5. Customers are standing in a queue to buy from you and order one at a time (in the order specified by bills). Each customer will only buy one lemonade and pay with either a $5, $10, or $20 bill. You must provide the correct change to each customer so that the net transaction is that the customer pays $5.
|
||||
|
||||
Note that you do not have any change in hand at first.
|
||||
|
||||
Given an integer array bills where bills[i] is the bill the ith customer pays, return true if you can provide every customer with the correct change, or false otherwise.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
本题是简单题, 比较直接的做法可以通过贪心来解决该题, 每次找钱时都先尝试找较大的金额, 再找较小的金额. 但实际上因为题目中只有三种额度的钱, 则可以直接枚举所有情况(一共三种), 即对10直接找一张5, 对20找一张10一张5或者三张5. 对20要先枚举找一张10的情况, 因为找10一定要和5搭配, 而找三张5则不受10的影响. 这种直接枚举速度比较快.
|
||||
|
||||
### 代码
|
||||
```go
|
||||
func lemonadeChange(bills []int) bool {
|
||||
money := make([]int, 2)
|
||||
for _,bill := range bills{
|
||||
if bill == 5{
|
||||
money[0]++
|
||||
}else if bill == 10{
|
||||
money[1]++
|
||||
money[0]--
|
||||
}else{
|
||||
if money[1] > 0{
|
||||
money[1]--
|
||||
money[0]--
|
||||
}else{
|
||||
money[0] -= 3
|
||||
}
|
||||
}
|
||||
if money[0] < 0 || money[1] < 0{
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
## day172 2024-08-16
|
||||
### 624. Maximum Distance in Arrays
|
||||
You are given m arrays, where each array is sorted in ascending order.
|
||||
|
||||
You can pick up two integers from two different arrays (each array picks one) and calculate the distance. We define the distance between two integers a and b to be their absolute difference |a - b|.
|
||||
|
||||
Return the maximum distance.
|
||||
|
||||

|
||||
|
||||
### 题解
|
||||
考虑到本题中各个数组已经从小到大排好序, 则直接使用每个数组的首位数字(最小数字)分别与其余数组的末位数字(最大数字)做差并取绝对值, 这些值中的最大值即为所求的值. 来考虑只有两个数组的情况, 如果数组1的最小值为a,最大值为b, 数组2的最小值为c, 最大值为d. 若a<b<c<d则最大差值为d-a, 若a<c<b<d, 则最大差值为d-a, 若a<c<d<b, 则最大差值为|d-a|和|b-c|二者中的较大值, 则无论哪种情况, 只要求出数组的最小值和其他数组最大值的差最终均能得到正确答案. 但这种解法需要将每个数组都与其他数组比较一遍, 此时可以想到因为数组都是排好序的, 则设定一个全局最小值, 在遍历各个数组的过程中用各个数组的最小值来更新全局最小值, 同理也可以得到一个全局最大值, 要先将全局最小值与当前数组的最大值和当前数组最小值与全局最大值做差后更新全局最大差值, 再用当前数组的值更新全局最值, 因为题目中要求必须从不同的数组中取两个数字, 则这样可以避免全局最小和全局最大值位于同一个数组当中. 如[[1,4],[0,5]]如果在取得全局最小值和全局最大值后再做差, 则因为0和5在同一个数组中, 并不满足题目条件. 而若先求出差再更新最小值和最大值, 则会得到正确答案.
|
||||
|
||||
### 代码
|
||||
```go
|
||||
func maxDistance(arrays [][]int) int {
|
||||
smallest := arrays[0][0]
|
||||
biggest := arrays[0][len(arrays[0])-1]
|
||||
result := 0
|
||||
for _, array := range arrays[1:]{
|
||||
n := len(array)
|
||||
result = max(abs(biggest-array[0]), result, abs(array[n-1]-smallest))
|
||||
smallest = min(array[0], smallest)
|
||||
biggest = max(array[n-1], biggest)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func abs(input int)int{
|
||||
if input < 0{
|
||||
return -input
|
||||
}
|
||||
return input
|
||||
}
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user