LeetBook 新手村笔记


LeetBook 新手村

笔记整理于:新手村

1480. 一维数组的动态和

题目1480. 一维数组的动态和

给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]…nums[i]) 。

请返回 nums 的动态和。

示例:

输入:nums = [1,2,3,4]
输出:[1,3,6,10]
解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4]

官方解答:

//第一步,第一个元素自然是不变,
//第二步,第二个元素变成第一个元素与原来第二个元素之和,
//第三步,(此时第二个元素已经是动态和了)第三个元素,变成第二个元素与原来第三个元素之和,以此类推

//第一步1,2,3,4
//第二步1,3,3,4
//第三步1,3,6,4
//第四步1,3,6,10
class Solution {
    public int[] runningSum(int[] nums) {
        for(int i = 1; i < nums.length; i++){
            nums[i] += nums[i - 1];
        }
        return nums;
    }
}

复杂度分析

  • 时间复杂度:O(n),其中 nn 是给定数组长度。
  • 空间复杂度:O(1)。我们只需要常数的空

精选解答:

1480. 一维数组的动态和(动态规划,清晰图解)

此题使用求和公式暴力求解的效率较低,因为包含大量重复计算。考虑借助「前一个动态和 f(i-1)f(i−1) 」来计算得到「当前动态和 f(i)f(i) 」,此题被约化为一个简单动态规划问题。

nums1234
i
dp13610
i-1i
// 细心的我们发现,如果原地修改 nums ,可以避免新建 dp 带来的内存开销。但通常情况下,不应改变输入变量,因此不建议原地修改 nums 数组。
//  dp[i] = dp[i - 1] + nums[i];
//内存消耗较大
class Solution {
    public int[] runningSum(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            dp[i] = dp[i - 1] + nums[i];
        }
        return dp;
    }
}

复杂度分析

  • 时间复杂度 O(N): 遍历 nums 使用线性时间。

  • 空间复杂度 O(1) : 用于保存结果的 dp 是必须使用的空间,此处不计入。

1342. 将数字变成 0 的操作次数

题目1342. 将数字变成 0 的操作次数

给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。

示例

输入:num = 14
输出:6
解释:
步骤 1) 14 是偶数,除以 2 得到 7 。
步骤 27 是奇数,减 1 得到 6 。
步骤 36 是偶数,除以 2 得到 3 。
步骤 43 是奇数,减 1 得到 2 。
步骤 52 是偶数,除以 2 得到 1 。
步骤 61 是奇数,减 1 得到 0

官方解答:

方法一:模拟
思路与算法

将 num 与 1 进行位运算来判断 num 的奇偶性。

class Solution {
    public int numberOfSteps(int num) {
        int ret = 0;
        while (num > 0) {
            ret += (num > 1 ? 1 : 0) + (num & 0x01);
            num >>= 1;
        }
        return ret;
    }
}

复杂度分析

  • 时间复杂度:O(lognum),其中 num 是输入数值。每次循环都将 num 的数值减半,因此时间复杂度为 O(lognum)。
  • 空间复杂度:O(1)。只需要常数空间。

大众解法

按位与运算符(&)
按位与运算将两个运算分量的对应位按位遵照以下规则进行计算:
 0 & 0 = 0, 
 0 & 1 = 0, 
 1 & 0 = 0, 
 1 & 1 = 1。

即同为 1 的位,结果为 1,否则结果为 0

例如,设3的内部表示为
00000011
5的内部表示为
00000101
则3&5的结果为
00000001
右移运算符(>>)

​ 右移运算将一个位串信息向右移指定的位,右端移出的位的信息被丢弃。例如12>>2,结果为3。与左移相反,对于小整数,每右移1位,相当于除以2。在右移时,需要注意符号位问题。对无符号数据,右移时,左端空出的位用0补充。对于带符号的数据,如果移位前符号位为0(正数),则左端也是用0补充;如果移位前符号位为1(负数),则左端用0或用1补充,取决于计算机系统。对于负数右移,称用0 补充的系统为“逻辑右移”,用1补充的系统为“算术右移”。

//c++
class Solution {
public:
    int numberOfSteps(int num) {
        int step = 0;
        while(num ){
            // if(num % 2 == 0){
            //     num /= 2;
            // }else{
            //     num--;
            // }
            (num & 1) == 0 ? num >>= 1 : --num;
            step++;
        }
        return step;
    }
};

补充知识:

1672. 最富有客户的资产总量

题目:1672. 最富有客户的资产总量

给你一个 m x n 的整数网格 accounts ,其中 accounts[i][j] 是第 i 位客户在第 j 家银行托管的资产数量。返回最富有客户所拥有的 资产总量 。

客户的 资产总量 就是他们在各家银行托管的资产数量之和。最富有客户就是 资产总量 最大的客户。

示例:

输入:accounts = [[1,2,3],[3,2,1]]
输出:6
解释:
第 1 位客户的资产总量 = 1 + 2 + 3 = 62 位客户的资产总量 = 3 + 2 + 1 = 6
两位客户都是最富有的,资产总量都是 6 ,所以返回 6

官方解答

分别计算每位客户在各家银行托管的资产数量之和,返回这些资产总量的最大值。

//java
class Solution {
    public int maximumWealth(int[][] accounts) {
        int maxWealth = Integer.MIN_VALUE;
        for (int[] account : accounts) {
            maxWealth = Math.max(maxWealth, Arrays.stream(account).sum());
        }
        return maxWealth;
    }
}
//c
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int maximumWealth(int** accounts, int accountsSize, int* accountsColSize){
    int maxWealth = INT_MIN;
    for (int i = 0; i < accountsSize; i++) {
        int sum = 0;
        for (int j = 0; j < accountsColSize[0]; j++) {
            sum += accounts[i][j];
        }
        maxWealth = MAX(maxWealth, sum);
    }
    return maxWealth;
}

复杂度分析

  • 时间复杂度:O(mn),其中 m 和 n 分别是网格 accounts 的行数和列数。
  • 空间复杂度:O(1)。

412. Fizz Buzz

题目:412. Fizz Buzz

给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中:

  • answer[i] == “FizzBuzz” 如果 i 同时是 3 和 5 的倍数。
  • answer[i] == “Fizz” 如果 i 是 3 的倍数。
  • answer[i] == “Buzz” 如果 i 是 5 的倍数。
  • answer[i] == i (以字符串形式)如果上述条件全不满足。

示例:

输入:n = 5
输出:["1","2","Fizz","4","Buzz"]

官方解答

class Solution {
    public List<String> fizzBuzz(int n) {
        List<String> answer = new ArrayList<String>();
        for (int i = 1; i <= n; i++) {
            StringBuffer sb = new StringBuffer();
            if (i % 3 == 0) {
                sb.append("Fizz");
            }
            if (i % 5 == 0) {
                sb.append("Buzz");
            }
            if (sb.length() == 0) {
                sb.append(i);
            }
            answer.add(sb.toString());
        }
        return answer;
    }
}

复杂度分析

  • 时间复杂度:O(n)。需要遍历从 1 到 n 的每个整数,对于每个整数 i,生成 answer[i] 的时间复杂度是 O(1)。

  • 空间复杂度:O(1)。注意返回值不计入空间复杂度。

笨办法

class Solution {
    public List<String> fizzBuzz(int n) {
    List<String> answer = new ArrayList();
    for(int i = 1;i<=n;++i){
        
        if(i %3 == 0 && i % 5 == 0){
            answer.add("FizzBuzz");
        }else {
            if(i %3 == 0){
            answer.add("Fizz");
            }
            if(i %5 == 0){
                answer.add("Buzz") ;
            }
        }

        if(i %5 != 0 && i % 3 !=0){
            answer.add(""+i);
        }
    }
    return answer;
    }
}

876. 链表的中间结点

题目:876. 链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例:

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3(测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

提示:

  • 给定链表的结点数介于 1100 之间。

官方题解

  • 方法一:数组

    思路和算法

    链表的缺点在于不能通过下标访问对应的元素。因此我们可以考虑对链表进行遍历,同时将遍历到的元素依次放入数组 A 中。如果我们遍历到了 N 个元素,那么链表以及数组的长度也为 N,对应的中间节点即为 A[N/2]。

    class Solution {
        public ListNode middleNode(ListNode head) {
            ListNode[] A = new ListNode[100];
            int t = 0;
            while (head != null) {
                A[t++] = head;
                head = head.next;
            }
            return A[t / 2];
        }
    }
    

    复杂度分析

    • 时间复杂度:O(N),其中 N 是给定链表中的结点数目。
    • 空间复杂度:O(N),即数组 A 用去的空间。
  • 方法二:单指针法

    我们可以对方法一进行空间优化,省去数组 A。

    我们可以对链表进行两次遍历。第一次遍历时,我们统计链表中的元素个数 N;第二次遍历时,我们遍历到第 N/2 个元素(链表的首节点为第 0 个元素)时,将该元素返回即可。

    class Solution {
        public ListNode middleNode(ListNode head) {
            int n = 0;
            ListNode cur = head;
            while (cur != null) {
                ++n;
                cur = cur.next;
            }
            int k = 0;
            cur = head;
            while (k < n / 2) {
                ++k;
                cur = cur.next;
            }
            return cur;
        }
    }
    

    复杂度分析

    • 时间复杂度:O(N),其中 NN 是给定链表的结点数目。
    • 空间复杂度:O(1),只需要常数空间存放变量和指针。
  • 方法三:快慢指针法

    思路和算法

    我们可以继续优化方法二,用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间。

    class Solution {
        public ListNode middleNode(ListNode head) {
            ListNode slow = head, fast = head;
            while (fast != null && fast.next != null) {
                slow = slow.next;
                fast = fast.next.next;
            }
            return slow;
        }
    }

    复杂度分析

    • 时间复杂度:O(N)O(N),其中 NN 是给定链表的结点数目。

    • 空间复杂度:O(1)O(1),只需要常数空间存放 slow 和 fast 两个指针。

快慢指针法分析

1673706455246

定义两个指针:

  • 快指针:每次走两步,当下一个节点或者当前节点为空时,停止
  • 慢指针:每次走一步,快指针停下时慢指针搜在的位置即为链表的中间位置

单链表的节点个数:

  • 奇数:快指针走到5的位置时,判断下一个为空(要求 fast.next != null),停止,此时慢指针为3,中间位置
  • 偶数:快指针走到5的位置时,判断下一个不为空,继续走下一步(到7的位置),慢指针也往下走一步(到4的位置),此时快指针为空(要求 fast != null ),停止,慢指针为4,中间位置

不管怎么样,慢指针都在中间位置,非常巧妙!

精选题解

383. 赎金信

题目:383. 赎金信

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例:

输入:ransomNote = "aa", magazine = "ab"
输出:false

输入:ransomNote = "aa", magazine = "aab"
输出:true

官方解答

字符统计:

题目要求使用字符串 magazine中的字符来构建新的字符串ransomNode ,且 ransomNode中的每个字符只能使用一次,只需要满足字符串 magazine中的每个英文字母(‘a’-‘z’) 的统计次数都大于等于 ransomNode中相同字母的统计次数即可。

  • 如果字符串 magazine的长度小于字符串 ransomNode的长度,则我们可以肯定magazine 无法构成 ransomNode,此时直接返回 false。
  • 首先统计magazine 中每个英文字母 a的次数cnt[a] ,再遍历统计ransomNode 中每个英文字母 的次数,如果发现 ransomNode中存在某个英文字母 c的统计次数大于magazine 中该字母统计次数cnt[c] ,则此时我们直接返回 false。
class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.length() > magazine.length()) {
            return false;
        }
        int[] cnt = new int[26];
        for (char c : magazine.toCharArray()) {
            cnt[c - 'a']++;
        }
        for (char c : ransomNote.toCharArray()) {
            cnt[c - 'a']--;
            if(cnt[c - 'a'] < 0) {
                return false;
            }
        }
        return true;
    }
}

复杂度分析

  • 时间复杂度:O(m + n),其中 m 是字符串ransomNote 的长度,n 是字符串 magazine 的长度,我们只需要遍历两个字符一次即可。

  • 空间复杂度:O(|S|),S 是字符集,这道题中 S 为全部小写英语字母,因此 |S| = 26。

分析

官方题解使用 长度为26的 int 数组 表示 26 个英文字母,判断 magazine 和 ransomNode 长度大小:

  • magazine < ransomNode :题目要求判断 ransomNote 能不能由 magazine 里面的字符构成magazine 字母个数少于 ransomNode 肯定不能构成
  • magazine < ransomNode:定义一个长度为26的 int数组,两次for循环,第一次把 magazine 中每个字母出现的次数记录到数组中,第二次把 ransomNode 中每个字母出现的次数与 数组中记录数相减,如果有负数说明不能够成

表格演示

以 ransomNote = “aa”, magazine = “ab” 为例:

ransomNote :两个a,数组表示:[2,0,0,0,…]

magazine : 一个a一个b,数组表示:[1,1,0,0,0,…]

第一次 for 循环后 数组变为:[1,1,…]

第二次 fo r循环后 数组变为:[-1,1,…]

ransomNode20...
magazine11...
int 数组cnt[0]cnt[1]cnt[2].....cnt[25]
字母abcdz

注意:

第一次 for 循环后 数组改变,第二次 for 循环 是在 现在数组基础上进行 相减,1-2=-1,有负数说明 a 字母数量不够,return false。

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GaleTeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值