Skip to content

Commit 9f108a8

Browse files
author
Dinghao LI
committed
035
1 parent 10c3213 commit 9f108a8

File tree

18 files changed

+706
-87
lines changed

18 files changed

+706
-87
lines changed

027-二叉搜索树与双向链表/problem027.go

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,34 @@ import (
44
"fmt"
55
)
66

7+
8+
79
type TreeNode struct {
8-
Val int
9-
Left *TreeNode
10-
Right *TreeNode
10+
Val int
11+
Left *TreeNode
12+
Right *TreeNode
1113
}
1214

13-
func TreeToList(root *TreeNode) (*TreeNode, *TreeNode) {
15+
func TreeToList(root *TreeNode) (*TreeNode, *TreeNode){
1416
head, tail := root, root
15-
if root == nil { return head, tail }
16-
if root.Left == nil && root.Right == nil {
17-
head.Left, tail.Right = root, root
18-
return head, tail
19-
}
20-
if root.Left != nil {
21-
leftHead, leftTail := TreeToList(root.Left)
22-
head = leftHead
23-
root.Left = leftTail
24-
leftTail.Right = root
25-
}
26-
if root.Right != nil {
27-
rightHead, rightTail := TreeToList(root.Right)
17+
if root == nil { return head, tail }
18+
if root.Left == nil && root.Right == nil {
19+
head.Left, tail.Right = root, root
20+
return head, tail
21+
}
22+
if root.Left != nil {
23+
leftHead, leftTail := TreeToList(root.Left)
24+
head = leftHead
25+
root.Left = leftTail
26+
leftTail.Right = root
27+
}
28+
if root.Right != nil {
29+
rightHead, rightTail := TreeToList(root.Right)
2830
tail = rightTail
29-
root.Right = rightHead
30-
rightHead.Left = root
31-
}
32-
head.Left, tail.Right = head, tail
31+
root.Right = rightHead
32+
rightHead.Left = root
33+
}
34+
head.Left, tail.Right = head, tail
3335
return head, tail
3436
}
3537

030-最小的K个数/README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
1-
#题意
1+
# 题意
22
题目描述
33

44
输入n个整数,找出其中最小的K个数。
55

66
例如输入4,5,1,6,2,7,3,8这8个数字, 则最小的4个数字是1,2,3,4,。
77

8-
#分析
8+
# 分析
99

10-
##方法一--排序
10+
## 方法一--排序
1111
要求一个序列中最小的K个数,按照惯有的思维方式,很简单,先对这个序列从小到大排序,然后输出前面的最小的K个数即可;
1212

1313
至于选取什么样的排序方法,第一时间应该想到的是快速排序,我们知道,快速排序平均时间复杂度为O(nlogn),然后再遍历序列中前K个元素输出,即可,总的时间复杂度为O(nlogn + k) = O(nlogn);——方法一
1414

15-
##方法二--选择或者交换排序
15+
## 方法二--选择或者交换排序
1616
再进一步想想,题目并没有要求要查找的k个数,甚至是后面的n-k个数是有序的,既然这样,咱们又何必对所有的n个数都进行排序呢? 这个时候,想到了选择或交换排序,即遍历n个数,先把最先遍历到的K个数存入大小为k的数组之中,对这k个数,利用选择或交换排序,找到k个数中的最大数Kmax(Kmax为这K个元素的数组中最大的元素),用时间为O(k)(你应该知道,插入或选择排序查找操作需要O(k)的时间),后再继续遍历后n-k个数,x与Kmax比较:如果x< Kmax,则x代替Kmax,并再次重新找出K个元素的数组中的最大元素Kmax';如果x>Kmax,则不更新数组。这样每次更新和不更新数组所用的时间为O(k)或O(0),整趟下来,总的时间复杂度平均下来为:nO(k) = O(nk);——方法二
1717

18-
##方法三--最小堆
18+
## 方法三--最大堆
1919
当然,更好的办法是维护k个元素的最大堆,原理与上述第2个方案一致,即用容量为K的最大堆存储最先遍历的K个数,并假设它们即是最小的K个数,建堆需要O(k)后,有k1)。继续遍历数列,每次遍历一个元素x,与堆顶元素比较,x),否则不更新堆。这样下来,总费时O(k+(n-k)logk) = O(nlogk)。此方法得益于在堆中,查找等各项操作时间复杂度均为logk(不然,就如上述思路2所述:直接用数组也可以找出前k个小的元素,用时O(nk));
2020

21-
##方法四--快速排序的分治划分(中位数作为枢轴)
21+
## 方法四--快速排序的分治划分(中位数作为枢轴)
2222
按编程之美第141页上解法二的所述,类似快速排序的划分方法,N个数存储在数组S中,再从数组中随机选取一个数X(随机选取枢纽元,可做到线性期望时间O(N)的复杂度),把数组划分为Sa和Sb两部分,Sa<= X <=Sb,如果要查找的K个小的元素小于Sa中的元素个数,则返回Sa中较小的K个元素,否则返回Sa中K个小的元素 + Sb中小的K-|Sa|个元素。像上述过程一样,这个运用类似快速排序的partition的快速选择Select算法寻找最小的K个元素,在最坏的情况下亦能做到O(N)的复杂度。
2323

2424
不过值得一提的是,这个快速选择Select算法是选择数组中“中位数的中位数”作为枢纽元,而非随机选择枢纽元;
2525

26-
##方法五--快速排序的分治划分(随机枢轴)
26+
## 方法五--快速排序的分治划分(随机枢轴)
2727
Randomized-Select,每次都是随机选择数列中的一个元素作为主元,在O(n)的时间内找到第K小的元素,然后遍历输出前面的K个小的元素。如果能的话,那么总的时间复杂度为线性期望时间:O(n+k) = O(n)(当n比较小时);
2828

29-
##方法六--线性排序
29+
## 方法六--线性排序
3030
线性时间的排序,即计数排序,时间复杂度虽能达到O(n),但是,限制条件太多了,不常用;
3131

32-
##方法七--最小堆与优先队列
32+
## 方法七--最小堆与优先队列
3333
”可以用最小堆初始化数组,然后取这个优先队列前k个值。复杂度为O(n)+kO(logn)“。意思是针对整个数组序列建立最小堆,建堆所用时间为O(n),然后取堆中的前k个数,即总的时间复杂度为:O(n+klogn)。
3434

35-
##方法八--提取最小堆的元素
35+
## 方法八--提取最小堆的元素
3636
与上述思路7类似,不同的是在对元素数组原地建立最小堆O(n)后,然后提取K次,但是每次提取时,换到顶部的元素只需要下移顶多K次就足够了,下移次数逐次减少(而上述思路7每次提取都需要logn,所有提取K次,思路7需要K*logn,而本思路8只需要K^2);

030-最小的K个数/problem030.go

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,41 @@
1-
package problem010
1+
package main
22

3-
import "errors"
3+
import (
4+
"fmt"
5+
"errors"
6+
. "../utils"
7+
)
48

5-
func getMostFreq(nums []int) (int, error){
6-
if len(nums) == 0 {
7-
return -1, errors.New("Array is empty")
9+
func getMostFreq(nums []int, k int) ([]int, error){
10+
res := []int{}
11+
if k > len(nums) {
12+
return res, errors.New("k > length of nums")
813
}
9-
count := 1
10-
value := nums[0]
11-
for i := 1; i < len(nums); i++ {
12-
if nums[i] == value {
13-
count++
14+
15+
maxHeap := NewMaxHeap()
16+
17+
for _, v := range nums {
18+
if maxHeap.Length() < k {
19+
maxHeap.Insert(v)
1420
} else {
15-
count--
16-
if count == 0 {
17-
value = nums[i]
18-
count = 1
21+
max, _ := maxHeap.Max()
22+
if max > v {
23+
maxHeap.DeleteMax()
24+
maxHeap.Insert(v)
1925
}
2026
}
2127
}
22-
return value, nil
28+
for maxHeap.Length()>0 {
29+
v, _ := maxHeap.DeleteMax()
30+
res = append(res, v)
31+
}
32+
return res, nil
33+
}
34+
35+
func main() {
36+
test := []int{1,2,3,4,5,6,7,8,9}
37+
fmt.Println(test)
38+
fmt.Println(getMostFreq(test, 3))
39+
fmt.Println(getMostFreq(test, 4))
40+
fmt.Println(getMostFreq(test, 5))
2341
}

030-最小的K个数/problem030_test.go

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# 题意
2+
3+
题目描述
4+
5+
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。
6+
7+
今天测试组开完会后,他又发话了:
8+
9+
在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。
10+
11+
但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?
12+
13+
例如:{6,-3,-2,7,-15,1,2,2}, 连续子向量的最大和为8(从第0个开始,到第3个为止)。 你会不会被他忽悠住?
14+
15+
16+
# 分析见题目注释
17+
18+
Leecode指路[LeetCode 53. Maximum Subarray
19+
](https://leetcode.com/problems/maximum-subarray/submissions/)
20+
21+
22+
23+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// dp[i] 代表以i结尾时最大连续子数组的最大和
8+
// dp[i] 初始值为nums[0]
9+
// 计算dp[i]时
10+
// 如果dp[i-1]>0: 那我们放心的加进来所以dp[i] = nums[i]+dp[i-1]
11+
// 否则:dp[i]取自己 dp[i] = nums[i]
12+
// 再利用全局变量,保存出出现过的最大值
13+
func MaxSubset(nums []int) int {
14+
if len(nums) == 0 { return 0 }
15+
dp := make([]int, len(nums))
16+
dp[0] = nums[0]
17+
res := nums[0]
18+
for i:=1; i<len(nums); i++{
19+
if dp[i-1] > 0 {
20+
dp[i] = nums[i]+dp[i-1]
21+
} else {
22+
dp[i] = nums[i]
23+
}
24+
25+
if dp[i] > res {
26+
res = dp[i]
27+
}
28+
}
29+
return res
30+
}
31+
32+
// 精简一些
33+
// dp[i] 代表以i结尾时最大连续子数组的最大和(同时)
34+
// 复制nums到dp
35+
// 如果dp[i-1]>0那就把dp[i]=dp[i-1]+dp[i]
36+
// 再利用全局变量,保存出出现过的最大值
37+
38+
func MaxSubset2(nums []int) int {
39+
if len(nums) == 0 { return 0 }
40+
dp := make([]int, len(nums))
41+
copy(dp, nums)
42+
max := dp[0]
43+
for i := 1; i < len(dp); i++ {
44+
if dp[i-1]>0{
45+
dp[i] = dp[i-1]+dp[i]
46+
}
47+
if dp[i]>max {
48+
max = dp[i]
49+
}
50+
}
51+
return max
52+
}
53+
54+
func max(a,b int) int {
55+
if a > b {
56+
return a
57+
}
58+
return b
59+
}
60+
61+
func main() {
62+
test := []int{1,-2,3,10,-4,7,2,-5}
63+
fmt.Println(test)
64+
fmt.Println(MaxSubset(test))
65+
fmt.Println(MaxSubset2(test))
66+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# 题意
2+
3+
题目描述
4+
5+
从1到n整数中1出现的次数
6+
7+
# 分治递归
8+
9+
我们重新分析下这个问题,
10+
11+
对于任意一个个位数n,只要n>=1,它就包含一个"1";
12+
13+
n<1,即n=0时,则包含的"1"的个数为0。
14+
15+
于是我们考虑用分治的思想将任意一个n位数不断缩小规模分解成许多个个位数,这样求解就很方便。
16+
17+
但是,我们该如何降低规模?
18+
19+
仔细分析,我们会发现,
20+
21+
**任意一个n位数中"1"的个位可以分解为两个n-1位数中"1"的个数的和,最后再加上一个与最高位数相关的常数C**
22+
例如,
23+
24+
对于n=12,可以拆分为0109,1012,即 f(12) = f(10 - 1) + f(12 - 10) + 3,其中3是表示最高位为1的数字个数,这里就是10,11,12,
25+
对于n=132,可以拆分为099,100132,即f(132)=f(100 -1) + f(132 - 100) + 33,33代表最高位为1的数字的个数,这里就是100-132百位数字的1出新了33次.
26+
27+
对于232,可以拆分为099,100232,即f(232) = 2\*f(100 - 1) + f(32) + 100,因为232大于199,所以它包括了所有最高位为1的数字即100-199,共100个。
28+
29+
综上,我们分析得出,最后加的常数C只跟最高位n1是否为1有关
30+
31+
当最高位为1时,常数C为原数字N去掉最高位后剩下的数字+1,如N=12时,$C = 2 + 1 = 3$,N=132时,$C = 32 + 1 = 33$
32+
33+
当最高位大于1时,常数C为$10^(bit-1)$,其中bit为N的位数,如N=232时,bit=3,$C = 10^(bit-1) = 10^2 = 100$。 于是,我们可以列出递归方程如下:
34+
35+
if(n1 == 1)
36+
f(n) = f(10bit-1) + f(n - 10bit) + n - 10bit+ 1;
37+
else
38+
f(n) = n1\*f(10bit-1) + f(n – n1\*10bit) + 10bit;
39+
进一步可以归结为
40+
41+
f(n) = n1\*f(10bit-1) + f(n – n1\*10bit) + LEFT;
42+
其中
43+
if(n1 == 1)
44+
LEFT = n - 10bit+ 1;
45+
else
46+
LEFT = 10bit;
47+
48+
此算法的优点是不用遍历1-N就可以得到f(N)。经过我测试,此算法的运算速度比解法一快了许多许多,数字在1010内时,算法都可以在毫秒级内结
49+
50+
51+
52+
53+

0 commit comments

Comments
 (0)