刷穿LeetCode——Task16

这篇博客记录刷题第16天的学习心得。

237. 删除链表中的节点

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。

现有一个链表 – head = [4,5,1,9],它可以表示为:
Linklist

示例 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<=2311
  • 这道题就是每次取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(m1)=f(m)+1 f ( m − 2 ) = f ( m ) + 1 f(m-2) = f(m) + 1 f(m2)=f(m)+1, f ( m − 3 ) = f ( m ) + 1 f(m-3) = f(m) + 1 f(m3)=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;
    }
};

猜你喜欢

转载自blog.csdn.net/qq_33007293/article/details/113356055