合并两个有序数组

题目描述

88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1nums2,另有两个整数 mn ,分别表示 nums1nums2 中的元素数目。

请你 合并 nums2nums1 中,使合并后的数组同样按 非递减顺序 排列。

**注意:**最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n

示例 1:

1
2
3
4
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3][2,5,6]
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

示例 2:

1
2
3
4
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1][]
合并结果是 [1]

示例 3:

1
2
3
4
5
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [][1]
合并结果是 [1]
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

提示:

  • nums1.length == m + n
  • nums2.length == n
  • 0 <= m, n <= 200
  • 1 <= m + n <= 200
  • -109 <= nums1[i], nums2[j] <= 109

**进阶:**你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?

题解

思路不多说,尾插法即可,较为简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func merge(nums1 []int, m int, nums2 []int, n int) {
//不额外占用空间,所以就是在num1上原地修改
ptr1, ptr2, ptr := m-1, n-1, m+n-1
for ;ptr1 >=0 && ptr2 >= 0;{
if nums1[ptr1]>nums2[ptr2]{
nums1[ptr] = nums1[ptr1]
ptr--
ptr1--
}else{
nums1[ptr] = nums2[ptr2]
ptr--
ptr2--
}
}
for ;ptr2>=0;{
nums1[ptr] = nums2[ptr2]
ptr--
ptr2--
}
}

移动元素

题目描述

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k

用户评测:

评测机将使用以下代码测试您的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。
// 它以不等于 val 的值排序。

int k = removeElement(nums, val); // 调用你的实现

assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
assert nums[i] == expectedNums[i];
}

如果所有的断言都通过,你的解决方案将会 通过

示例 1:

1
2
3
4
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

示例 2:

1
2
3
4
5
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

题解

原地移除且允许顺序变换,那就是简单的元素位置交换,思路不多说,主要看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func removeElement(nums []int, val int) int {
lst := len(nums)-1
for i := 0; i<=lst; i++{
if nums[i] != val {
continue
}
for {
if lst>=0&&nums[lst]==val{
lst--
}else{
break
}
}
if i<lst{
tmp := nums[i]
nums[i] = nums[lst]
nums[lst] = tmp
}
}
return lst+1;
}

删除有序数组中的重复项

题目描述

26. 删除有序数组中的重复项

给你一个 非严格递增排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k

判题标准:

系统会用下面的代码来测试你的题解:

1
2
3
4
5
6
7
8
9
int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
assert nums[i] == expectedNums[i];
}

如果所有断言都通过,那么您的题解将被 通过

示例 1:

1
2
3
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2:

1
2
3
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按 非严格递增 排列

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func removeDuplicates(nums []int) int {
//特例,额外处理
if len(nums)<=1{
return len(nums)
}
//有效数字
start := 0
for ptr := 1; ptr<len(nums); ptr++{
if nums[ptr]!=nums[start]{
start++
nums[start] = nums[ptr]
}
}
return start+1
}

删除有序数组的重复项II

题目描述

80. 删除有序数组中的重复项 II

给你一个有序数组 nums ,请你** 原地** 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以**「引用」**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

1
2
3
4
5
6
7
8
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}

示例 1:

1
2
3
输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。

示例 2:

1
2
3
输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新长度 length = 7, 并且原数组的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。不需要考虑数组中超出新长度后面的元素。

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按升序排列

题解

这题其实本质上和上一题相同,不过多解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func removeDuplicates(nums []int) int {
if len(nums)<=2{
return len(nums)
}
start := 1
for ptr := 2; ptr<len(nums); ptr++{
//这个对比的是start-1,不可以是ptr-2
if nums[ptr] != nums[start-1]{
start++
nums[start] = nums[ptr]
}
}
return start+1

}

多数元素

题目描述

169. 多数元素

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

1
2
输入:nums = [3,2,3]
输出:3

示例 2:

1
2
输入:nums = [2,2,1,1,1,2,2]
输出:2

提示:

  • n == nums.length
  • 1 <= n <= 5 * 104
  • -109 <= nums[i] <= 109

**进阶:**尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

题解

典型的思维题,解决方式也比较简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func majorityElement(nums []int) int {
res := -1
count := 0
for _,val := range nums{
if val==res{
count++;
}else{
if count==0{
res = val
count++
}else{
count--
}
}
}
return res
}

轮转数组

题目描述

189. 轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

1
2
3
4
5
6
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]

示例 2:

1
2
3
4
5
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]

提示:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1
  • 0 <= k <= 105

进阶:

  • 尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
  • 你可以使用空间复杂度为 O(1)原地 算法解决这个问题吗?

题解

其实这也是个思维题,我们可以尝试用呆板的方式解决,或者也可以用trick,这里两种题解都放一下

Trick解法:翻转数组

1
2
3
4
5
6
7
8
9
10
11
12
13
func reverse(nums []int, start int, end int){
for start < end {
nums[start], nums[end] = nums[end], nums[start]
start++
end--
}
}
func rotate(nums []int, k int) {
k = k%len(nums)
reverse(nums,0,len(nums)-1)
reverse(nums,0,k-1)
reverse(nums,k,len(nums)-1)
}

写到这里其实已经很感觉出来go是个很简洁的语言了,主要感觉还是有一些库啥的不太熟练,比如我就不知道go有没有优先级队列

传统解法,看似呆瓜实际全是技巧

听我说,这种方法真的写起来很累,真的很累

同时写到这里的时候,我发现一个问题

在 Go 中,:= 是短变量声明操作符,用于在同一作用域内声明并初始化变量。Go 的设计允许在嵌套作用域(如循环块、条件块等)中重复使用 := 声明同名变量,但需要注意:

  • 在 Go 中,变量的作用域由其声明的位置决定。
  • 如果在一个嵌套作用域(如循环体或条件块)中使用 := 声明一个与外部作用域同名的变量,则会屏蔽(shadow) 外部作用域中的变量。
  • 当嵌套作用域结束时,屏蔽效果消失,外部作用域的变量重新可见。
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
func rotate(nums []int, k int)  {
n := len(nums)
if n<=1{
return
}
k = k%n
start := 0
//记录安置好的元素个数
count := 0
//从第一个开始安置
ptr := start
for count<len(nums){
ptr = (ptr+k)%n
if ptr == start{
//第start圈循环的最后一个,安置到start位置
//记得安置完数目+1
count++
start++
ptr = start
continue
}
tmp := nums[start]
nums[start] = nums[ptr]
nums[ptr] = tmp
count++
}
}

买卖股票的最佳时机

121. 买卖股票的最佳时机

题目描述

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 1:

1
2
3
4
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

1
2
3
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 104

题解

经典题,go不提供Max的比较方式,同时,MaxInt、MinInt是在math包下

1
2
3
4
5
6
7
8
9
10
11
12
13
func maxProfit(prices []int) int {
preMin := math.MaxInt
res := math.MinInt
for _,value := range prices{
if value<preMin{
preMin = value
}
if res<value-preMin{
res = value-preMin
}
}
return res
}