这篇博客记录刷题第16天的学习心得。
237. 删除链表中的节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
现有一个链表 – head = [4,5,1,9],它可以表示为:
示例 1:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.示例 2:
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:给定你链表中值为 1
的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
- 这道题乍一看,输入参数怎么只有一个,没有头结点吗?
- 常规做法是给定头结点head和待删除节点node,然后用
head->next->val == node->val
判断下一个是否是待删除节点node,然后head->next = head->next->next
- 而这道题的输入只有待删除节点(非末尾)node,且函数返回
viod
型,因此直接根据单链表中节点之间仅存在逻辑连接的特性原位操作,把node变成node的下一个节点(下一节点val复制到node,再把下一节点跳过)。c++代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
};
238. 除自身以外数组的乘积
给你一个长度为 n 的整数数组
nums
,其中 n > 1,返回输出数组output
,其中output[i]
等于nums
中除nums[i]
之外其余各元素的乘积。示例:
输入: [1,2,3,4] 输出: [24,12,8,6]
提示: 题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶: 你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
- 这题最简单的思路是二重遍历,对每个元素都遍历求除自身以外数组的乘积,但题目要求 O ( n ) O(n) O(n) 时间复杂度;且如果先求数组乘积,然后一重遍历将乘积除以当前元素,又是不允许的。
- 看了评论区,才发现一种很巧妙的 O ( n ) O(n) O(n)时间复杂度 、 O ( 1 ) O(1) O(1)空间复杂度方法,定义双指针 l l l, r r r,使 l l l 对应当前元素的左边数字乘积, r r r 对应当前元素的右边数字乘积,分两次一重遍历赋值给当前元素,就能使得输出数组当前元素等于除自身以外数组的乘积。代码如下:
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> outnums(n, 1); //输出数组初始化
int l = 1, r = 1;
for (int i = 0; i < n; i++) {
//第一轮记录左边数字乘积
outnums[i] *= l;
l *= nums[i];
}
for (int i = n-1; i >= 0; i--) {
//第二轮记录右边数字乘积
outnums[i] *= r;
r *= nums[i];
}
return outnums;
}
};
292. Nim 游戏
你和你的朋友,两个人一起玩 Nim 游戏:
- 桌子上有一堆石头。
- 你们轮流进行自己的回合,你作为先手。
- 每一回合,轮到的人拿掉 1 - 3 块石头。
- 拿掉最后一块石头的人就是获胜者。
假设你们每一步都是最优解。请编写一个函数,来判断你是否可以在给定石头数量为 n 的情况下赢得游戏。如果可以赢,返回 true;否则,返回 false 。
示例 1:
输入:n = 4 输出:false 解释:如果堆中有 4 块石头,那么你永远不会赢得比赛;
因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走。示例 2:
输入:n = 1
输出:true示例 3:
输入:n = 2
输出:true提示:
- 1 < = n < = 2 31 − 1 1 <= n <= 2^{31} - 1 1<=n<=231−1
- 这道题就是每次取m块石头(m可取0, 1, 2,每次m都不是固定的),如果恰好自己能在奇数次取完就赢了。本来想用类似于
(n % m) % 2 != 0
之类的判断语句,但由于每次m都不是固定的,无法直接判断; - 还打算用动态规划, f ( m ) f(m) f(m) 对应还剩 m 块石头时进行的次数,发现无法建立确切的递推关系: f ( m − 1 ) = f ( m ) + 1 f(m-1) = f(m) + 1 f(m−1)=f(m)+1, f ( m − 2 ) = f ( m ) + 1 f(m-2) = f(m) + 1 f(m−2)=f(m)+1, f ( m − 3 ) = f ( m ) + 1 f(m-3) = f(m) + 1 f(m−3)=f(m)+1都行,又得作罢;
- 看了题解,感觉这道题不考算法,纯粹是数字分析+脑筋急转弯。直接上结论,输入n为4的倍数就注定无法获胜,只要n不为4倍数的情况(不给你的对手留下4的倍数的石块),就有可能赢,函数返回true!推理如下:
让我们考虑一些小例子。显而易见的是,如果石头堆中只有一块、两块、或是三块石头,那么在你的回合,你就可以把全部石子拿走,从而在游戏中取胜。而如果就像题目描述那样,堆中恰好有四块石头,你就会失败。因为在这种情况下不管你取走多少石头,总会为你的对手留下几块,使得他可以在游戏中打败你。因此,要想获胜,在你的回合中,必须避免石头堆中的石子数为4 的情况。
同样地,如果有五块、六块、或是七块石头,你可以控制自己拿取的石头数,总是恰好给你的对手留下四块石头,使他输掉这场比赛。但是如果石头堆里有八块石头,你就不可避免地会输掉,因为不管你从一堆石头中挑出一块、两块还是三块,你的对手都可以选择三块、两块或一块,以确保在再一次轮到你的时候,你会面对四块石头。
显然,它以相同的模式不断重复 n = 4 , 8 , 12 , 16 , … n=4,8,12,16,… n=4,8,12,16,… 基本可以看出是 4 的倍数。
代码只有一行:
class Solution {
public:
bool canWinNim(int n) {
return n % 4 != 0;
}
};