阅读视图

发现新文章,点击刷新页面。

每日一题-使绳子变成彩色的最短时间🟡

Alice 把 n 个气球排列在一根绳子上。给你一个下标从 0 开始的字符串 colors ,其中 colors[i] 是第 i 个气球的颜色。

Alice 想要把绳子装扮成 五颜六色的 ,且她不希望两个连续的气球涂着相同的颜色,所以她喊来 Bob 帮忙。Bob 可以从绳子上移除一些气球使绳子变成 彩色 。给你一个 下标从 0 开始 的整数数组 neededTime ,其中 neededTime[i] 是 Bob 从绳子上移除第 i 个气球需要的时间(以秒为单位)。

返回 Bob 使绳子变成 彩色 需要的 最少时间

 

示例 1:

输入:colors = "abaac", neededTime = [1,2,3,4,5]
输出:3
解释:在上图中,'a' 是蓝色,'b' 是红色且 'c' 是绿色。
Bob 可以移除下标 2 的蓝色气球。这将花费 3 秒。
移除后,不存在两个连续的气球涂着相同的颜色。总时间 = 3 。

示例 2:

输入:colors = "abc", neededTime = [1,2,3]
输出:0
解释:绳子已经是彩色的,Bob 不需要从绳子上移除任何气球。

示例 3:

输入:colors = "aabaa", neededTime = [1,2,3,4,1]
输出:2
解释:Bob 会移除下标 0 和下标 4 处的气球。这两个气球各需要 1 秒来移除。
移除后,不存在两个连续的气球涂着相同的颜色。总时间 = 1 + 1 = 2 。

 

提示:

  • n == colors.length == neededTime.length
  • 1 <= n <= 105
  • 1 <= neededTime[i] <= 104
  • colors 仅由小写英文字母组成

贪心,每段保留最大的(Python/Java/C++/C/Go/JS/Rust)

为了不让相邻气球颜色相同,对于 $\textit{colors}$ 的每个连续同色段,只能保留一个气球。

贪心地,保留其中耗时最大的气球。

答案为 $\textit{neededTime}$ 的总和,减去每段的最大耗时。

###py

class Solution:
    def minCost(self, colors: str, neededTime: List[int]) -> int:
        ans = max_t = 0
        for i, t in enumerate(neededTime):
            ans += t
            if t > max_t:  # 手写 if 比调用 max 快
                max_t = t
            if i == len(colors) - 1 or colors[i] != colors[i + 1]:
                # 遍历到了连续同色段的末尾
                ans -= max_t  # 保留耗时最大的气球
                max_t = 0  # 准备计算下一段的最大耗时
        return ans

###java

class Solution {
    public int minCost(String colors, int[] neededTime) {
        int n = neededTime.length;
        int ans = 0;
        int maxT = 0;
        for (int i = 0; i < n; i++) {
            int t = neededTime[i];
            ans += t;
            maxT = Math.max(maxT, t);
            if (i == n - 1 || colors.charAt(i) != colors.charAt(i + 1)) {
                // 遍历到了连续同色段的末尾
                ans -= maxT; // 保留耗时最大的气球
                maxT = 0; // 准备计算下一段的最大耗时
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int minCost(string colors, vector<int>& neededTime) {
        int n = colors.size();
        int ans = 0, max_t = 0;
        for (int i = 0; i < n; i++) {
            int t = neededTime[i];
            ans += t;
            max_t = max(max_t, t);
            if (i == n - 1 || colors[i] != colors[i + 1]) {
                // 遍历到了连续同色段的末尾
                ans -= max_t; // 保留耗时最大的气球
                max_t = 0; // 准备计算下一段的最大耗时
            }
        }
        return ans;
    }
};

###c

#define MAX(a, b) ((b) > (a) ? (b) : (a))

int minCost(char* colors, int* neededTime, int neededTimeSize) {
    int ans = 0, max_t = 0;
    for (int i = 0; i < neededTimeSize; i++) {
        int t = neededTime[i];
        ans += t;
        max_t = MAX(max_t, t);
        if (i == neededTimeSize - 1 || colors[i] != colors[i + 1]) {
            // 遍历到了连续同色段的末尾
            ans -= max_t; // 保留耗时最大的气球
            max_t = 0; // 准备计算下一段的最大耗时
        }
    }
    return ans;
}

###go

func minCost(colors string, neededTime []int) (ans int) {
maxT := 0
for i, t := range neededTime {
ans += t
maxT = max(maxT, t)
if i == len(colors)-1 || colors[i] != colors[i+1] {
// 遍历到了连续同色段的末尾
ans -= maxT // 保留耗时最大的气球
maxT = 0    // 准备计算下一段的最大耗时
}
}
return
}

###js

var minCost = function(colors, neededTime) {
    const n = colors.length;
    let ans = 0, maxT = 0;
    for (let i = 0; i < n; i++) {
        const t = neededTime[i];
        ans += t;
        maxT = Math.max(maxT, t);
        if (i === n - 1 || colors[i] !== colors[i + 1]) {
            // 遍历到了连续同色段的末尾
            ans -= maxT; // 保留耗时最大的气球
            maxT = 0; // 准备计算下一段的最大耗时
        }
    }
    return ans;
};

###rust

impl Solution {
    pub fn min_cost(colors: String, needed_time: Vec<i32>) -> i32 {
        let s = colors.as_bytes();
        let mut ans = 0;
        let mut max_t = 0;
        for (i, t) in needed_time.into_iter().enumerate() {
            ans += t;
            max_t = max_t.max(t);
            if i + 1 == s.len() || s[i] != s[i + 1] {
                // 遍历到了连续同色段的末尾
                ans -= max_t; // 保留耗时最大的气球
                max_t = 0; // 准备计算下一段的最大耗时
            }
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{colors}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

专题训练

见下面双指针题单的「六、分组循环」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

使绳子变成彩色的最短时间

方法一:贪心

思路与算法

根据题意可以知道,如果字符串 $\textit{colors}$ 中有若干相邻的重复颜色,则这些颜色中最多只能保留一个。因此,我们可以采取贪心的策略:在这一系列重复颜色中,我们保留删除成本最高的颜色,并删除其他颜色。这样得到的删除成本一定是最低的。

代码

###C++

class Solution {
public:
    int minCost(string colors, vector<int>& neededTime) {
        int i = 0, len = colors.length();
        int ret = 0;
        while (i < len) {
            char ch = colors[i];
            int maxValue = 0;
            int sum = 0;

            while (i < len && colors[i] == ch) {
                maxValue = max(maxValue, neededTime[i]);
                sum += neededTime[i];
                i++;
            }
            ret += sum - maxValue;
        }
        return ret;
    }
};

###Java

class Solution {
    public int minCost(String colors, int[] neededTime) {
        int i = 0, len = colors.length();
        int ret = 0;
        while (i < len) {
            char ch = colors.charAt(i);
            int maxValue = 0;
            int sum = 0;

            while (i < len && colors.charAt(i) == ch) {
                maxValue = Math.max(maxValue, neededTime[i]);
                sum += neededTime[i];
                i++;
            }
            ret += sum - maxValue;
        }
        return ret;
    }
}

###C#

public class Solution {
    public int MinCost(string colors, int[] neededTime) {
        int i = 0, len = colors.Length;
        int ret = 0;
        while (i < len) {
            char ch = colors[i];
            int maxValue = 0;
            int sum = 0;

            while (i < len && colors[i] == ch) {
                maxValue = Math.Max(maxValue, neededTime[i]);
                sum += neededTime[i];
                i++;
            }
            ret += sum - maxValue;
        }
        return ret;
    }
}

###Python

class Solution:
    def minCost(self, colors: str, neededTime: List[int]) -> int:
        i = 0
        length = len(colors)
        ret = 0

        while i < length:
            ch = colors[i]
            maxValue = 0
            total = 0

            while i < length and colors[i] == ch:
                maxValue = max(maxValue, neededTime[i])
                total += neededTime[i]
                i += 1
            
            ret += total - maxValue
        
        return ret

###C

int minCost(char* colors, int* neededTime, int neededTimeSize) {
    int i = 0;
    int ret = 0;
    while (i < neededTimeSize) {
        char ch = colors[i];
        int maxValue = 0;
        int sum = 0;

        while (i < neededTimeSize && colors[i] == ch) {
            maxValue = fmax(maxValue, neededTime[i]);
            sum += neededTime[i];
            i++;
        }
        ret += sum - maxValue;
    }
    return ret;
}

###Go

func minCost(colors string, neededTime []int) int {
    i, n := 0, len(colors)
    ret := 0
    for i < n {
        ch := colors[i]
        maxValue := 0
        sum := 0
        
        for i < n && colors[i] == ch {
            if neededTime[i] > maxValue {
                maxValue = neededTime[i]
            }
            sum += neededTime[i]
            i++
        }
        ret += sum - maxValue
    }
    return ret
}

###JavaScript

var minCost = function(colors, neededTime) {
    let i = 0, len = colors.length;
    let ret = 0;
    while (i < len) {
        const ch = colors[i];
        let maxValue = 0;
        let sum = 0;

        while (i < len && colors[i] === ch) {
            maxValue = Math.max(maxValue, neededTime[i]);
            sum += neededTime[i];
            i++;
        }
        ret += sum - maxValue;
    }
    return ret;
};

###TypeScript

function minCost(colors: string, neededTime: number[]): number {
    let i = 0, len = colors.length;
    let ret = 0;
    while (i < len) {
        const ch = colors[i];
        let maxValue = 0;
        let sum = 0;

        while (i < len && colors[i] === ch) {
            maxValue = Math.max(maxValue, neededTime[i]);
            sum += neededTime[i];
            i++;
        }
        ret += sum - maxValue;
    }
    return ret;
};

###Rust

impl Solution {
    pub fn min_cost(colors: String, needed_time: Vec<i32>) -> i32 {
        let mut i = 0;
        let len = colors.len();
        let mut ret = 0;
        let colors = colors.chars().collect::<Vec<char>>();
        
        while i < len {
            let ch = colors[i];
            let mut max_value = 0;
            let mut sum = 0;

            while i < len && colors[i] == ch {
                max_value = max_value.max(needed_time[i]);
                sum += needed_time[i];
                i += 1;
            }
            ret += sum - max_value;
        }
        ret
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 为字符串的长度。我们只需对字符串进行一次线性的扫描。

  • 空间复杂度:$O(1)$。我们只开辟了常量大小的空间。

C++ 一次遍历

解题思路

遍历,找到相同的字母,取成本小的,并将没有消费的成本放在下一次比较的字符成本中。

代码

###cpp

class Solution {
public:
    int minCost(string s, vector<int>& cost) {
        int n = s.size();
        int sum = 0;
        for(int i = 0;i<n-1;i++)
        {
            if(s[i] == s[i+1])
            {
                sum+= min(cost[i],cost[i+1]); 
                if(cost[i]>cost[i+1])swap(cost[i],cost[i+1]);
            }
        }
        return sum;
    }
};

每日一题-从链表中移除在数组中存在的节点🟡

给你一个整数数组 nums 和一个链表的头节点 head。从链表中移除所有存在于 nums 中的节点后,返回修改后的链表的头节点。

 

示例 1:

输入: nums = [1,2,3], head = [1,2,3,4,5]

输出: [4,5]

解释:

移除数值为 1, 2 和 3 的节点。

示例 2:

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

输出: [2,2,2]

解释:

移除数值为 1 的节点。

示例 3:

输入: nums = [5], head = [1,2,3,4]

输出: [1,2,3,4]

解释:

链表中不存在值为 5 的节点。

 

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 105
  • nums 中的所有元素都是唯一的。
  • 链表中的节点数在 [1, 105] 的范围内。
  • 1 <= Node.val <= 105
  • 输入保证链表中至少有一个值没有在 nums 中出现过。

哨兵节点+一次遍历(Python/Java/C++/C/Go/JS/Rust)

如何在遍历链表的同时,删除链表节点?请看【基础算法精讲 08】

对于本题,由于直接判断节点值是否在 $\textit{nums}$ 中,需要遍历 $\textit{nums}$,时间复杂度为 $\mathcal{O}(n)$。把 $\textit{nums}$ 中的元素保存一个哈希集合中,然后判断节点值是否在哈希集合中,这样可以做到 $\mathcal{O}(1)$。

具体做法:

  1. 把 $\textit{nums}$ 中的元素保存到一个哈希集合中。
  2. 由于头节点可能会被删除,在头节点前面插入一个哨兵节点 $\textit{dummy}$,以简化代码逻辑。
  3. 初始化 $\textit{cur} = \textit{dummy}$。
  4. 遍历链表,如果 $\textit{cur}$ 的下一个节点的值在哈希集合中,则需要删除,更新 $\textit{cur}.\textit{next}$ 为 $\textit{cur}.\textit{next}.\textit{next}$;否则不删除,更新 $\textit{cur}$ 为 $\textit{cur}.\textit{next}$。
  5. 循环结束后,返回 $\textit{dummy}.\textit{next}$。

注:$\textit{dummy}$ 和 $\textit{cur}$ 是同一个节点的引用,修改 $\textit{cur}.\textit{next}$ 也会修改 $\textit{dummy}.\textit{next}$。

本题视频讲解,欢迎点赞关注~

###py

class Solution:
    def modifiedList(self, nums: List[int], head: Optional[ListNode]) -> Optional[ListNode]:
        st = set(nums)
        cur = dummy = ListNode(next=head)
        while cur.next:
            nxt = cur.next
            if nxt.val in st:
                cur.next = nxt.next  # 从链表中删除 nxt 节点
            else:
                cur = nxt  # 不删除 nxt,继续向后遍历链表
        return dummy.next

###java

class Solution {
    public ListNode modifiedList(int[] nums, ListNode head) {
        Set<Integer> set = new HashSet<>(nums.length, 1); // 预分配空间
        for (int x : nums) {
            set.add(x);
        }

        ListNode dummy = new ListNode(0, head);
        ListNode cur = dummy;
        while (cur.next != null) {
            ListNode nxt = cur.next;
            if (set.contains(nxt.val)) {
                cur.next = nxt.next; // 从链表中删除 nxt 节点
            } else {
                cur = nxt; // 不删除 nxt,继续向后遍历链表
            }
        }
        return dummy.next;
    }
}

###cpp

class Solution {
public:
    ListNode* modifiedList(vector<int>& nums, ListNode* head) {
        unordered_set<int> st(nums.begin(), nums.end());
        ListNode dummy(0, head);
        ListNode* cur = &dummy;
        while (cur->next) {
            ListNode* nxt = cur->next;
            if (st.contains(nxt->val)) {
                cur->next = nxt->next; // 从链表中删除 nxt 节点
            } else {
                cur = nxt; // 不删除 nxt,继续向后遍历链表
            }
        }
        return dummy.next;
    }
};

###c

#define MAX(a, b) ((b) > (a) ? (b) : (a))

struct ListNode* modifiedList(int* nums, int numsSize, struct ListNode* head) {
    int mx = 0;
    for (int i = 0; i < numsSize; i++) {
        mx = MAX(mx, nums[i]);
    }

    bool* has = calloc(mx + 1, sizeof(bool));
    for (int i = 0; i < numsSize; i++) {
        has[nums[i]] = true;
    }

    struct ListNode dummy = {0, head};
    struct ListNode* cur = &dummy;
    while (cur->next) {
        struct ListNode* nxt = cur->next;
        if (nxt->val <= mx && has[nxt->val]) {
            cur->next = nxt->next; // 从链表中删除 nxt 节点
            free(nxt);
        } else {
            cur = nxt; // 不删除 nxt,继续向后遍历链表
        }
    }

    free(has);
    return dummy.next;
}

###go

func modifiedList(nums []int, head *ListNode) *ListNode {
has := make(map[int]bool, len(nums)) // 预分配空间
for _, x := range nums {
has[x] = true
}

dummy := &ListNode{Next: head}
cur := dummy
for cur.Next != nil {
nxt := cur.Next
if has[nxt.Val] {
cur.Next = nxt.Next // 从链表中删除 nxt 节点
} else {
cur = nxt // 不删除 nxt,继续向后遍历链表
}
}
return dummy.Next
}

###js

var modifiedList = function(nums, head) {
    const set = new Set(nums);
    const dummy = new ListNode(0, head);
    let cur = dummy;
    while (cur.next) {
        const nxt = cur.next;
        if (set.has(nxt.val)) {
            cur.next = nxt.next; // 从链表中删除 nxt 节点
        } else {
            cur = nxt; // 不删除 nxt,继续向后遍历链表
        }
    }
    return dummy.next;
};

###rust

use std::collections::HashSet;

impl Solution {
    pub fn modified_list(nums: Vec<i32>, head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
        let set = nums.into_iter().collect::<HashSet<_>>();
        let mut dummy = Box::new(ListNode { val: 0, next: head });
        let mut cur = &mut dummy;
        while let Some(ref mut nxt) = cur.next {
            if set.contains(&nxt.val) {
                cur.next = nxt.next.take(); // 从链表中删除 nxt 节点
            } else {
                cur = cur.next.as_mut()?; // 不删除 nxt,继续向后遍历链表
            }
        }
        dummy.next
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n + m)$,其中 $n$ 是 $\textit{nums}$ 的长度,$m$ 是链表的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

专题训练

见下面链表题单的「§1.2 删除节点」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

模拟

解法:模拟

不会有人链表题真的去操作原链表吧?把链表里的数提取出来,按题意算出答案后新建一个链表即可。复杂度 $\mathcal{O}(n)$。

参考代码(c++)

###cpp

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* modifiedList(vector<int>& nums, ListNode* head) {
        // 按题意模拟
        unordered_set<int> st;
        for (int x : nums) st.insert(x);
        vector<int> vec;
        for (; head != nullptr; head = head->next) if (st.count(head->val) == 0) vec.push_back(head->val);

        // 根据答案 vec 中新建一个链表
        ListNode *dummy = new ListNode(), *now = dummy;
        for (int x : vec) {
            now->next = new ListNode(x);
            now = now->next;
        }
        return dummy->next;
    }
};

数字小镇中的捣蛋鬼

方法一:哈希表

使用哈希表统计 $\textit{nums}$ 中出现了两次的数字,返回结果。

###C++

class Solution {
public:
    vector<int> getSneakyNumbers(vector<int>& nums) {
        vector<int> res;
        unordered_map<int, int> count;
        for (int x : nums) {
            count[x]++;
            if (count[x] == 2) {
                res.push_back(x);
            }
        }
        return res;
    }
};

###Go

func getSneakyNumbers(nums []int) []int {
    res := []int{}
    count := make(map[int]int)
    for _, x := range nums {
        count[x]++
        if count[x] == 2 {
            res = append(res, x)
        }
    }
    return res
}

###Python

class Solution:
    def getSneakyNumbers(self, nums: List[int]) -> List[int]:
        res = []
        count = {}
        for x in nums:
            count[x] = count.get(x, 0) + 1
            if count[x] == 2:
                res.append(x)
        return res

###Java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        List<Integer> res = new ArrayList<>();
        Map<Integer, Integer> count = new HashMap<>();
        for (int x : nums) {
            count.put(x, count.getOrDefault(x, 0) + 1);
            if (count.get(x) == 2) {
                res.add(x);
            }
        }
        return res.stream().mapToInt(i -> i).toArray();
    }
}

###TypeScript

function getSneakyNumbers(nums: number[]): number[] {
    const res: number[] = [];
    const count = new Map<number, number>();
    for (const x of nums) {
        count.set(x, (count.get(x) || 0) + 1);
        if (count.get(x) === 2) {
            res.push(x);
        }
    }
    return res;
}

###JavaScript

var getSneakyNumbers = function(nums) {
    const res = [];
    const count = new Map();
    for (const x of nums) {
        count.set(x, (count.get(x) || 0) + 1);
        if (count.get(x) === 2) {
            res.push(x);
        }
    }
    return res;
};

###C#

public class Solution {
    public int[] GetSneakyNumbers(int[] nums) {
        List<int> res = new List<int>();
        Dictionary<int, int> count = new Dictionary<int, int>();
        foreach (int x in nums) {
            if (!count.ContainsKey(x)) count[x] = 0;
            count[x]++;
            if (count[x] == 2) {
                res.Add(x);
            }
        }
        return res.ToArray();
    }
}

###C

int* getSneakyNumbers(int* nums, int numsSize, int* returnSize) {
    int* res = (int*)malloc(2 * sizeof(int));
    int* count = (int*)calloc(101, sizeof(int));
    *returnSize = 0;
    for (int i = 0; i < numsSize; i++) {
        int x = nums[i];
        count[x]++;
        if (count[x] == 2) {
            res[(*returnSize)++] = x;
        }
    }
    free(count);
    return res;
}

###Rust

impl Solution {
    pub fn get_sneaky_numbers(nums: Vec<i32>) -> Vec<i32> {
        let mut res = Vec::new();
        let mut count = std::collections::HashMap::new();
        for x in nums {
            let c = count.entry(x).or_insert(0);
            *c += 1;
            if *c == 2 {
                res.push(x);
            }
        }
        res
    }
}

复杂度分析

  • 时间复杂度:$O(n)$。

  • 空间复杂度:$O(n)$。

方法二:位运算

我们将 $\textit{nums}$ 的所有数字和 $0$ 到 $n - 1$ 的所有数字进行异或,那么计算结果为两个额外多出现一次的数字的异或值 $y$。那么两个数字最低不相同的位为 $\textit{lowBit} = y \land -y$,利用 $\textit{lowBit}$ 将 $\textit{nums}$ 的所有数字和 $0$ 到 $n - 1$ 的所有数字分成两部分,然后分别计算这两部分数字的异或值,即为结果。

###C++

class Solution {
public:
    vector<int> getSneakyNumbers(vector<int>& nums) {
        int n = (int)nums.size() - 2;
        int y = 0;
        for (int x : nums) {
            y ^= x;
        }
        for (int i = 0; i < n; i++) {
            y ^= i;
        }
        int lowBit = y & (-y);
        int x1 = 0, x2 = 0;
        for (int x : nums) {
            if (x & lowBit) {
                x1 ^= x;
            } else {
                x2 ^= x;
            }
        }
        for (int i = 0; i < n; i++) {
            if (i & lowBit) {
                x1 ^= i;
            } else {
                x2 ^= i;
            }
        }
        return {x1, x2};
    }
};

###Go

func getSneakyNumbers(nums []int) []int {
    n := len(nums) - 2
    y := 0
    for _, x := range nums {
        y ^= x
    }
    for i := 0; i < n; i++ {
        y ^= i
    }
    lowBit := y & -y
    x1, x2 := 0, 0
    for _, x := range nums {
        if x&lowBit != 0 {
            x1 ^= x
        } else {
            x2 ^= x
        }
    }
    for i := 0; i < n; i++ {
        if i&lowBit != 0 {
            x1 ^= i
        } else {
            x2 ^= i
        }
    }
    return []int{x1, x2}
}

###Python

class Solution:
    def getSneakyNumbers(self, nums: List[int]) -> List[int]:
        n = len(nums) - 2
        y = 0
        for x in nums:
            y ^= x
        for i in range(n):
            y ^= i
        lowBit = y & -y
        x1 = x2 = 0
        for x in nums:
            if x & lowBit:
                x1 ^= x
            else:
                x2 ^= x
        for i in range(n):
            if i & lowBit:
                x1 ^= i
            else:
                x2 ^= i
        return [x1, x2]

###Java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        int n = nums.length - 2;
        int y = 0;
        for (int x : nums) {
            y ^= x;
        }
        for (int i = 0; i < n; i++) {
            y ^= i;
        }
        int lowBit = y & -y;
        int x1 = 0, x2 = 0;
        for (int x : nums) {
            if ((x & lowBit) != 0) {
                x1 ^= x;
            } else {
                x2 ^= x;
            }
        }
        for (int i = 0; i < n; i++) {
            if ((i & lowBit) != 0) {
                x1 ^= i;
            } else {
                x2 ^= i;
            }
        }
        return new int[]{x1, x2};
    }
}

###TypeScript

function getSneakyNumbers(nums: number[]): number[] {
    const n = nums.length - 2;
    let y = 0;
    for (const x of nums) {
        y ^= x;
    }
    for (let i = 0; i < n; i++) {
        y ^= i;
    }
    const lowBit = y & -y;
    let x1 = 0, x2 = 0;
    for (const x of nums) {
        if (x & lowBit) {
            x1 ^= x;
        } else {
            x2 ^= x;
        }
    }
    for (let i = 0; i < n; i++) {
        if (i & lowBit) {
            x1 ^= i;
        } else {
            x2 ^= i;
        }
    }
    return [x1, x2];
}

###JavaScript

function getSneakyNumbers(nums) {
    const n = nums.length - 2;
    let y = 0;
    for (const x of nums) {
        y ^= x;
    }
    for (let i = 0; i < n; i++) {
        y ^= i;
    }
    const lowBit = y & -y;
    let x1 = 0, x2 = 0;
    for (const x of nums) {
        if (x & lowBit) {
            x1 ^= x;
        } else {
            x2 ^= x;
        }
    }
    for (let i = 0; i < n; i++) {
        if (i & lowBit) {
            x1 ^= i;
        } else {
            x2 ^= i;
        }
    }
    return [x1, x2];
}

###C#

public class Solution {
    public int[] GetSneakyNumbers(int[] nums) {
        int n = nums.Length - 2;
        int y = 0;
        foreach (int x in nums) {
            y ^= x;
        }
        for (int i = 0; i < n; i++) {
            y ^= i;
        }
        int lowBit = y & -y;
        int x1 = 0, x2 = 0;
        foreach (int x in nums) {
            if ((x & lowBit) != 0) {
                x1 ^= x;
            } else {
                x2 ^= x;
            }
        }
        for (int i = 0; i < n; i++) {
            if ((i & lowBit) != 0) {
                x1 ^= i;
            } else {
                x2 ^= i;
            }
        }
        return new int[] { x1, x2 };
    }
}

###C

int* getSneakyNumbers(int* nums, int numsSize, int* returnSize) {
    int n = numsSize - 2;
    int y = 0;
    for (int i = 0; i < numsSize; i++) {
        y ^= nums[i];
    }
    for (int i = 0; i < n; i++) {
        y ^= i;
    }
    int lowBit = y & -y;
    int x1 = 0, x2 = 0;
    for (int i = 0; i < numsSize; i++) {
        if (nums[i] & lowBit) {
            x1 ^= nums[i];
        } else {
            x2 ^= nums[i];
        }
    }
    for (int i = 0; i < n; i++) {
        if (i & lowBit) {
            x1 ^= i;
        } else {
            x2 ^= i;
        }
    }
    int* res = (int*)malloc(2 * sizeof(int));
    res[0] = x1;
    res[1] = x2;
    *returnSize = 2;
    return res;
}

###Rust

impl Solution {
    pub fn get_sneaky_numbers(nums: Vec<i32>) -> Vec<i32> {
        let n = nums.len() as i32 - 2;
        let mut y = 0;
        for &x in &nums {
            y ^= x;
        }
        for i in 0..n {
            y ^= i;
        }
        let low_bit = y & -y;
        let mut x1 = 0;
        let mut x2 = 0;
        for &x in &nums {
            if x & low_bit != 0 {
                x1 ^= x;
            } else {
                x2 ^= x;
            }
        }
        for i in 0..n {
            if i & low_bit != 0 {
                x1 ^= i;
            } else {
                x2 ^= i;
            }
        }
        vec![x1, x2]
    }
}

复杂度分析

  • 时间复杂度:$O(n)$。

  • 空间复杂度:$O(1)$。

方法三:数学

令两个额外多出现一次的数字为 $x_1$ 和 $x_2$。$\textit{nums}$ 的和与平方和分别为 $\textit{sum}$ 和 $\textit{squaredSum}$,从 $0$ 到 $n-1$ 的整数和与平方和分别为 $\frac{n(n-1)}{2}$ 和 $\frac{n(n-1)(2n-1)}{6}$。记 $\textit{sum}_2 = \textit{sum} - \frac{n(n-1)}{2}$ 和 $\textit{squaredSum}_2 = \textit{squaredSum} - \frac{n(n-1)(2n-1)}{6}$,那么有以下方程:

$$
\begin{cases}
x_1 + x_2 = \textit{sum}_2 \
x_1^2 + x_2^2 = \textit{squaredSum}_2
\end{cases}
$$

解得:

$$
\begin{cases}
x_1 = \frac{\textit{sum}_2 - \sqrt{2 \times \textit{squaredSum}_2 - \textit{sum}_2^2}}{2} \
x_2 = \frac{\textit{sum}_2 + \sqrt{2 \times \textit{squaredSum}_2 - \textit{sum}_2^2}}{2}
\end{cases}
$$

###C++

class Solution {
public:
    vector<int> getSneakyNumbers(vector<int>& nums) {
        int n = (int)nums.size() - 2;
        int sum = 0, squaredSum = 0;
        for (int x : nums) {
            sum += x;
            squaredSum += x * x;
        }
        int sum2 = sum - n * (n - 1) / 2;
        int squaredSum2 = squaredSum - n * (n - 1) * (2 * n - 1) / 6;
        int x1 = (sum2 - sqrt(2 * squaredSum2 - sum2 * sum2)) / 2;
        int x2 = (sum2 + sqrt(2 * squaredSum2 - sum2 * sum2)) / 2;
        return {x1, x2};
    }
};

###Go

func getSneakyNumbers(nums []int) []int {
    n := len(nums) - 2
    sum, squaredSum := 0.0, 0.0
    for _, x := range nums {
        sum += float64(x)
        squaredSum += float64(x * x)
    }
    sum2 := sum - float64(n * (n - 1) / 2)
    squaredSum2 := squaredSum - float64(n * (n - 1) * (2 * n - 1) / 6)
    x1 := (sum2 - math.Sqrt(2 * squaredSum2 - sum2 * sum2)) / 2
    x2 := (sum2 + math.Sqrt(2 * squaredSum2 - sum2 * sum2)) / 2
    return []int{int(x1), int(x2)}
}

###Python

class Solution:
    def getSneakyNumbers(self, nums: List[int]) -> List[int]:
        n = len(nums) - 2
        sum_ = sum(nums)
        squared_sum = sum(x*x for x in nums)
        sum2 = sum_ - n*(n-1)//2
        squared_sum2 = squared_sum - n*(n-1)*(2*n-1)//6
        x1 = (sum2 - math.sqrt(2*squared_sum2 - sum2*sum2)) / 2
        x2 = (sum2 + math.sqrt(2*squared_sum2 - sum2*sum2)) / 2
        return [int(x1), int(x2)]

###Java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        int n = nums.length - 2;
        double sum = 0, squaredSum = 0;
        for (int x : nums) {
            sum += x;
            squaredSum += x * x;
        }
        double sum2 = sum - n * (n - 1) / 2.0;
        double squaredSum2 = squaredSum - n * (n - 1) * (2 * n - 1) / 6.0;
        int x1 = (int)((sum2 - Math.sqrt(2 * squaredSum2 - sum2 * sum2)) / 2);
        int x2 = (int)((sum2 + Math.sqrt(2 * squaredSum2 - sum2 * sum2)) / 2);
        return new int[]{x1, x2};
    }
}

###TypeScript

function getSneakyNumbers(nums: number[]): number[] {
    const n = nums.length - 2;
    let sum = 0, squaredSum = 0;
    for (const x of nums) {
        sum += x;
        squaredSum += x * x;
    }
    const sum2 = sum - n * (n - 1) / 2;
    const squaredSum2 = squaredSum - n * (n - 1) * (2 * n - 1) / 6;
    const x1 = (sum2 - Math.sqrt(2 * squaredSum2 - sum2 * sum2)) / 2;
    const x2 = (sum2 + Math.sqrt(2 * squaredSum2 - sum2 * sum2)) / 2;
    return [Math.floor(x1), Math.floor(x2)];
}

###JavaScript

function getSneakyNumbers(nums) {
    const n = nums.length - 2;
    let sum = 0, squaredSum = 0;
    for (const x of nums) {
        sum += x;
        squaredSum += x * x;
    }
    const sum2 = sum - n * (n - 1) / 2;
    const squaredSum2 = squaredSum - n * (n - 1) * (2 * n - 1) / 6;
    const x1 = (sum2 - Math.sqrt(2 * squaredSum2 - sum2 * sum2)) / 2;
    const x2 = (sum2 + Math.sqrt(2 * squaredSum2 - sum2 * sum2)) / 2;
    return [Math.floor(x1), Math.floor(x2)];
}

###C#

public class Solution {
    public int[] GetSneakyNumbers(int[] nums) {
        int n = nums.Length - 2;
        double sum = 0, squaredSum = 0;
        foreach (int x in nums) {
            sum += x;
            squaredSum += x * x;
        }
        double sum2 = sum - n * (n - 1) / 2.0;
        double squaredSum2 = squaredSum - n * (n - 1) * (2 * n - 1) / 6.0;
        int x1 = (int)((sum2 - Math.Sqrt(2 * squaredSum2 - sum2 * sum2)) / 2);
        int x2 = (int)((sum2 + Math.Sqrt(2 * squaredSum2 - sum2 * sum2)) / 2);
        return new int[]{x1, x2};
    }
}

###C

int* getSneakyNumbers(int* nums, int numsSize, int* returnSize) {
    int n = numsSize - 2;
    double sum = 0, squaredSum = 0;
    for (int i = 0; i < numsSize; i++) {
        sum += nums[i];
        squaredSum += nums[i] * nums[i];
    }
    double sum2 = sum - n * (n - 1) / 2.0;
    double squaredSum2 = squaredSum - n * (n - 1) * (2 * n - 1) / 6.0;
    int x1 = (int)((sum2 - sqrt(2 * squaredSum2 - sum2 * sum2)) / 2);
    int x2 = (int)((sum2 + sqrt(2 * squaredSum2 - sum2 * sum2)) / 2);
    int* res = (int*)malloc(2 * sizeof(int));
    res[0] = x1;
    res[1] = x2;
    *returnSize = 2;
    return res;
}

###Rust

impl Solution {
    pub fn get_sneaky_numbers(nums: Vec<i32>) -> Vec<i32> {
        let n = nums.len() as i32 - 2;
        let sum: f64 = nums.iter().map(|&x| x as f64).sum();
        let squared_sum: f64 = nums.iter().map(|&x| (x*x) as f64).sum();
        let sum2 = sum - (n * (n - 1) / 2) as f64;
        let squared_sum2 = squared_sum - (n * (n - 1) * (2 * n - 1) / 6) as f64;
        let x1 = (sum2 - ((2.0 * squared_sum2 - sum2 * sum2).sqrt())) / 2.0;
        let x2 = (sum2 + ((2.0 * squared_sum2 - sum2 * sum2).sqrt())) / 2.0;
        vec![x1 as i32, x2 as i32]
    }
}

复杂度分析

  • 时间复杂度:$O(n)$。

  • 空间复杂度:$O(1)$。

[Python3/Java/C++/Go/TypeScript] 一题双解:计数 & 位运算(清晰题解)

方法一:计数

我们可以用一个数组 $\textit{cnt}$ 记录每个数字出现的次数。

遍历数组 $\textit{nums}$,当某个数字出现次数为 $2$ 时,将其加入答案数组中。

遍历结束后,返回答案数组即可。

###python

class Solution:
    def getSneakyNumbers(self, nums: List[int]) -> List[int]:
        cnt = Counter(nums)
        return [x for x, v in cnt.items() if v == 2]

###java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        int[] ans = new int[2];
        int[] cnt = new int[100];
        int k = 0;
        for (int x : nums) {
            if (++cnt[x] == 2) {
                ans[k++] = x;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    vector<int> getSneakyNumbers(vector<int>& nums) {
        vector<int> ans;
        int cnt[100]{};
        for (int x : nums) {
            if (++cnt[x] == 2) {
                ans.push_back(x);
            }
        }
        return ans;
    }
};

###go

func getSneakyNumbers(nums []int) (ans []int) {
cnt := [100]int{}
for _, x := range nums {
cnt[x]++
if cnt[x] == 2 {
ans = append(ans, x)
}
}
return
}

###ts

function getSneakyNumbers(nums: number[]): number[] {
    const ans: number[] = [];
    const cnt: number[] = Array(100).fill(0);
    for (const x of nums) {
        if (++cnt[x] > 1) {
            ans.push(x);
        }
    }
    return ans;
}

时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\textit{nums}$ 的长度。


方法二:位运算

设数组 $\textit{nums}$ 的长度为 $n + 2$,其中包含 $0 \sim n - 1$ 的整数,且有两个数字出现了两次。

我们可以通过异或运算来找出这两个数字。首先,我们对数组 $\textit{nums}$ 中的所有数字以及 $0 \sim n - 1$ 的整数进行异或运算,得到的结果为这两个重复数字的异或值,记为 $xx$。

接下来,我们可以通过 $xx$ 找到这两个数字的某些特征,进而将它们分开。具体步骤如下:

  1. 找到 $xx$ 的二进制表示中最低位或最高位的 $1$ 的位置,记为 $k$。这个位置表示这两个数字在该位上是不同的。
  2. 根据第 $k$ 位的值,将数组 $\textit{nums}$ 中的数字以及 $0 \sim n - 1$ 的整数分成两组:一组在第 $k$ 位上为 $0$,另一组在第 $k$ 位上为 $1$。然后分别对这两组数字进行异或运算,得到的结果即为这两个重复数字。

###python

class Solution:
    def getSneakyNumbers(self, nums: List[int]) -> List[int]:
        n = len(nums) - 2
        xx = nums[n] ^ nums[n + 1]
        for i in range(n):
            xx ^= i ^ nums[i]
        k = xx.bit_length() - 1
        ans = [0, 0]
        for x in nums:
            ans[x >> k & 1] ^= x
        for i in range(n):
            ans[i >> k & 1] ^= i
        return ans

###java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        int n = nums.length - 2;
        int xx = nums[n] ^ nums[n + 1];
        for (int i = 0; i < n; ++i) {
            xx ^= i ^ nums[i];
        }
        int k = Integer.numberOfTrailingZeros(xx);
        int[] ans = new int[2];
        for (int x : nums) {
            ans[x >> k & 1] ^= x;
        }
        for (int i = 0; i < n; ++i) {
            ans[i >> k & 1] ^= i;
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    vector<int> getSneakyNumbers(vector<int>& nums) {
        int n = nums.size() - 2;
        int xx = nums[n] ^ nums[n + 1];
        for (int i = 0; i < n; ++i) {
            xx ^= i ^ nums[i];
        }
        int k = __builtin_ctz(xx);
        vector<int> ans(2);
        for (int x : nums) {
            ans[(x >> k) & 1] ^= x;
        }
        for (int i = 0; i < n; ++i) {
            ans[(i >> k) & 1] ^= i;
        }
        return ans;
    }
};

###go

func getSneakyNumbers(nums []int) []int {
n := len(nums) - 2
xx := nums[n] ^ nums[n+1]
for i := 0; i < n; i++ {
xx ^= i ^ nums[i]
}
k := bits.TrailingZeros(uint(xx))
ans := make([]int, 2)
for _, x := range nums {
ans[(x>>k)&1] ^= x
}
for i := 0; i < n; i++ {
ans[(i>>k)&1] ^= i
}
return ans
}

###ts

function getSneakyNumbers(nums: number[]): number[] {
    const n = nums.length - 2;
    let xx = nums[n] ^ nums[n + 1];
    for (let i = 0; i < n; ++i) {
        xx ^= i ^ nums[i];
    }
    const k = Math.clz32(xx & -xx) ^ 31;
    const ans = [0, 0];
    for (const x of nums) {
        ans[(x >> k) & 1] ^= x;
    }
    for (let i = 0; i < n; ++i) {
        ans[(i >> k) & 1] ^= i;
    }
    return ans;
}

时间复杂度 $O(n)$,其中 $n$ 为数组 $\textit{nums}$ 的长度。空间复杂度 $O(1)$。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈 😄~

每日一题-数字小镇中的捣蛋鬼🟢

数字小镇 Digitville 中,存在一个数字列表 nums,其中包含从 0n - 1 的整数。每个数字本应 只出现一次,然而,有 两个 顽皮的数字额外多出现了一次,使得列表变得比正常情况下更长。

为了恢复 Digitville 的和平,作为小镇中的名侦探,请你找出这两个顽皮的数字。

返回一个长度为 2 的数组,包含这两个数字(顺序任意)。

 

示例 1:

输入: nums = [0,1,1,0]

输出: [0,1]

解释:

数字 0 和 1 分别在数组中出现了两次。

示例 2:

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

输出: [2,3]

解释:

数字 2 和 3 分别在数组中出现了两次。

示例 3:

输入: nums = [7,1,5,4,3,4,6,0,9,5,8,2]

输出: [4,5]

解释:

数字 4 和 5 分别在数组中出现了两次。

 

提示:

  • 2 <= n <= 100
  • nums.length == n + 2
  • 0 <= nums[i] < n
  • 输入保证 nums 恰好 包含两个重复的元素。

3289. 数字小镇中的捣蛋鬼

解法一

思路和算法

最简单的思路是使用哈希集合存储数组 $\textit{nums}$ 中出现过的数字。遍历数组 $\textit{nums}$ 中的每个数字,如果该数字尚未在哈希集合中则将其加入哈希集合,如果该数字已经在哈希集合中则将其加入答案数组。遍历结束之后,即可得到两个重复出现的数字。

代码

###Java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        int[] duplicates = new int[2];
        int index = 0;
        Set<Integer> set = new HashSet<Integer>();
        for (int num : nums) {
            if (!set.add(num)) {
                duplicates[index] = num;
                index++;
            }
        }
        return duplicates;
    }
}

###C#

public class Solution {
    public int[] GetSneakyNumbers(int[] nums) {
        int[] duplicates = new int[2];
        int index = 0;
        ISet<int> set = new HashSet<int>();
        foreach (int num in nums) {
            if (!set.Add(num)) {
                duplicates[index] = num;
                index++;
            }
        }
        return duplicates;
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 中的不同整数个数。数组 $\textit{nums}$ 的长度是 $n + 2$,需要遍历数组一次。

  • 空间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 中的不同整数个数。哈希表的空间是 $O(n)$。

解法二

思路和算法

用 $x$ 和 $y$ 表示两个重复出现的数字,$0 \le x, y < n$ 且 $x \ne y$。数组 $\textit{nums}$ 的长度是 $n + 2$,其中 $x$ 和 $y$ 各出现两次,其余每个数字各出现一次。如果在数组 $\textit{nums}$ 的 $n + 2$ 个元素以外再添加 $0$ 到 $n - 1$ 的每个数字各一次,则有 $2n + 2$ 个元素,其中 $x$ 和 $y$ 各出现三次,其余每个数字各出现两次。

根据按位异或运算的性质,如果一个数字被计算奇数次则该数字会对按位异或运算结果产生一次影响,如果一个数字被计算偶数次则该数字不会对按位异或运算结果产生影响,因此上述 $2n + 2$ 个元素的按位异或运算结果为 $\textit{xorVal} = x \oplus y$。由于 $x \ne y$,因此 $\textit{xorVal} \ne 0$。

得到 $\textit{xorVal}$ 之后,计算 $\textit{lowbit} = \textit{xorVal} ~&~ (-\textit{xorVal})$,则 $\textit{lowbit}$ 为 $\textit{xorVal}$ 的二进制表示的最低位的 $1$。为了区分 $x$ 和 $y$,可以规定 $\textit{lowbit}$ 在 $x$ 中的对应位是 $1$,$\textit{lowbit}$ 在 $y$ 中的对应位是 $0$。

得到 $\textit{lowbit}$ 之后,再次遍历上述 $2n + 2$ 个元素并计算 $x$ 和 $y$。对于每个整数 $\textit{num}$,执行如下操作。

  • 如果 $\textit{num} ~&~ \textit{lowbit}$ 的结果不为 $0$,则将 $x$ 的值更新为 $x \oplus \textit{num}$。

  • 如果 $\textit{num} ~&~ \textit{lowbit}$ 的结果为 $0$,则将 $y$ 的值更新为 $y \oplus \textit{num}$。

如果 $\textit{num}$ 是出现两次的数字,则会对 $x$ 或 $y$ 执行两次异或运算,不会改变 $x$ 和 $y$ 的值。如果 $\textit{num}$ 是出现三次的数字,则使用 $\textit{num}$ 更新 $x$ 或 $y$ 的值。遍历结束之后,即可得到 $x$ 和 $y$ 的值。

代码

###Java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        int[] duplicates = new int[2];
        int xorValue = 0;
        int n = nums.length - 2;
        for (int num : nums) {
            xorValue ^= num;
        }
        for (int i = 0; i < n; i++) {
            xorValue ^= i;
        }
        int lowbit = xorValue & (-xorValue);
        for (int num : nums) {
            update(duplicates, num, lowbit);
        }
        for (int i = 0; i < n; i++) {
            update(duplicates, i, lowbit);
        }
        return duplicates;
    }

    public void update(int[] duplicates, int num, int lowbit) {
        if ((num & lowbit) != 0) {
            duplicates[0] ^= num;
        } else {
            duplicates[1] ^= num;
        }
    }
}

###C#

public class Solution {
    public int[] GetSneakyNumbers(int[] nums) {
        int[] duplicates = new int[2];
        int xorValue = 0;
        int n = nums.Length - 2;
        foreach (int num in nums) {
            xorValue ^= num;
        }
        for (int i = 0; i < n; i++) {
            xorValue ^= i;
        }
        int lowbit = xorValue & (-xorValue);
        foreach (int num in nums) {
            Update(duplicates, num, lowbit);
        }
        for (int i = 0; i < n; i++) {
            Update(duplicates, i, lowbit);
        }
        return duplicates;
    }

    public void Update(int[] duplicates, int num, int lowbit) {
        if ((num & lowbit) != 0) {
            duplicates[0] ^= num;
        } else {
            duplicates[1] ^= num;
        }
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 中的不同整数个数。数组 $\textit{nums}$ 的长度是 $n + 2$,计算按位异或运算需要遍历 $4n + 4$ 个数字。

  • 空间复杂度:$O(1)$。

解法三

思路和算法

用 $x$ 和 $y$ 表示两个重复出现的数字。将数组 $\textit{nums}$ 中的所有元素的和记为 $\textit{sum}$,所有元素的平方和记为 $\textit{squaredSum}$,由于从 $0$ 到 $n - 1$ 的所有整数的和等于 $\dfrac{n(n - 1)}{2}$,所有整数的平方和等于 $\dfrac{n(n - 1)(2n - 1)}{6}$,因此有 $x + y = \textit{remainSum} = \textit{sum} - \dfrac{n(n - 1)}{2}$,$x^2 + y^2 = \textit{remainSquaredSum} = \textit{squaredSum} - \dfrac{n(n - 1)(2n - 1)}{6}$。问题转换成计算如下二元二次方程组的解。

$$
\begin{cases}
x + y = \textit{remainSum} \\
x^2 + y^2 = \textit{remainSquaredSum}
\end{cases}
$$

由于 $x \ne y$ 且将 $x$ 和 $y$ 的值可以交换,因此可以规定 $x < y$,该二元二次方程组的解如下。

$$
\begin{cases}
x = \dfrac{\textit{remainSum} - \sqrt{2 \times \textit{remainSquaredSum} - \textit{remainSum}^2}}{2} \\
y = \dfrac{\textit{remainSum} + \sqrt{2 \times \textit{remainSquaredSum} - \textit{remainSum}^2}}{2}
\end{cases}
$$

代码

###Java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        int[] duplicates = new int[2];
        int n = nums.length - 2;
        int sum = 0, squaredSum = 0;
        for (int num : nums) {
            sum += num;
            squaredSum += num * num;
        }
        int remainSum = sum - n * (n - 1) / 2;
        int remainSquaredSum = squaredSum - n * (n - 1) * (2 * n - 1) / 6;
        duplicates[0] = (int) ((remainSum - Math.sqrt(2 * remainSquaredSum - remainSum * remainSum)) / 2);
        duplicates[1] = (int) ((remainSum + Math.sqrt(2 * remainSquaredSum - remainSum * remainSum)) / 2);
        return duplicates;
    }
}

###C#

public class Solution {
    public int[] GetSneakyNumbers(int[] nums) {
        int[] duplicates = new int[2];
        int n = nums.Length - 2;
        int sum = 0, squaredSum = 0;
        foreach (int num in nums) {
            sum += num;
            squaredSum += num * num;
        }
        int remainSum = sum - n * (n - 1) / 2;
        int remainSquaredSum = squaredSum - n * (n - 1) * (2 * n - 1) / 6;
        duplicates[0] = (int) ((remainSum - Math.Sqrt(2 * remainSquaredSum - remainSum * remainSum)) / 2);
        duplicates[1] = (int) ((remainSum + Math.Sqrt(2 * remainSquaredSum - remainSum * remainSum)) / 2);
        return duplicates;
    }
}

复杂度分析

代码中使用了 $\texttt{Math.sqrt}$ 方法,该方法调用了本地方法 $\texttt{StrictMath.sqrt}$,因此其时间与空间复杂度取决于本地方法的实现,这里将该方法的时间复杂度和空间复杂度视为 $O(1)$。

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 中的不同整数个数。数组 $\textit{nums}$ 的长度是 $n + 2$,需要遍历数组一次。

  • 空间复杂度:$O(1)$。

O(1) 空间做法:位运算 / 数学(Python/Java/C++/Go)

本题和 2965. 找出缺失和重复的数字 本质是一样的,见 我的题解,有位运算和数学两种做法。

位运算

需要两次遍历。一次遍历见下面的数学做法。

###py

class Solution:
    def getSneakyNumbers(self, nums: List[int]) -> List[int]:
        n = len(nums) - 2
        xor_all = n ^ (n + 1)  # n 和 n+1 多异或了
        for i, x in enumerate(nums):
            xor_all ^= i ^ x
        shift = xor_all.bit_length() - 1

        ans = [0, 0]
        for i, x in enumerate(nums):
            if i < n:
                ans[i >> shift & 1] ^= i
            ans[x >> shift & 1] ^= x
        return ans

###java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        int n = nums.length - 2;
        int xorAll = n ^ (n + 1); // n 和 n+1 多异或了
        for (int i = 0; i < nums.length; i++) {
            xorAll ^= i ^ nums[i];
        }
        int shift = Integer.numberOfTrailingZeros(xorAll);

        int[] ans = new int[2];
        for (int i = 0; i < nums.length; i++) {
            if (i < n) {
                ans[i >> shift & 1] ^= i;
            }
            ans[nums[i] >> shift & 1] ^= nums[i];
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    vector<int> getSneakyNumbers(vector<int>& nums) {
        int n = nums.size() - 2;
        int xor_all = n ^ (n + 1); // n 和 n+1 多异或了
        for (int i = 0; i < nums.size(); i++) {
            xor_all ^= i ^ nums[i];
        }
        int shift = __builtin_ctz(xor_all);

        vector<int> ans(2);
        for (int i = 0; i < nums.size(); i++) {
            if (i < n) {
                ans[i >> shift & 1] ^= i;
            }
            ans[nums[i] >> shift & 1] ^= nums[i];
        }
        return ans;
    }
};

###go

func getSneakyNumbers(nums []int) []int {
n := len(nums) - 2
xorAll := n ^ (n + 1) // n 和 n+1 多异或了
for i, x := range nums {
xorAll ^= i ^ x
}
shift := bits.TrailingZeros(uint(xorAll))

ans := make([]int, 2)
for i, x := range nums {
if i < n {
ans[i>>shift&1] ^= i
}
ans[x>>shift&1] ^= x
}
return ans
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

数学

设多出的两个数分别为 $x$ 和 $y$。

也就是说,$\textit{nums} = [0,1,2,\cdots,n-1,x,y]$。

设 $\textit{nums}$ 的元素和为 $s$,$\textit{nums}$ 的元素平方之和为 $s_2$,那么有

$$
\begin{aligned}
&x+y = s - (0 + 1 + 2 + \cdots + n-1) = a \
&x^2+y^2 = s_2 - (0^2 + 1^2 + 2^2 + \cdots + (n-1)^2) = b \
\end{aligned}
$$

解得

$$
\begin{cases}
x = \dfrac{a-\sqrt{2b-a^2}}{2} \
y = \dfrac{a+\sqrt{2b-a^2}}{2} \
\end{cases}
$$

也可以先算出 $x$,然后算出 $y=a-x$。

本题视频讲解,欢迎点赞关注~

###py

class Solution:
    def getSneakyNumbers(self, nums: List[int]) -> List[int]:
        n = len(nums) - 2
        a = -n * (n - 1) // 2
        b = -n * (n - 1) * (n * 2 - 1) // 6
        for x in nums:
            a += x
            b += x * x
        x = (a - isqrt(b * 2 - a * a)) // 2
        return [x, a - x]

###java

class Solution {
    public int[] getSneakyNumbers(int[] nums) {
        int n = nums.length - 2;
        int a = -n * (n - 1) / 2;
        int b = -n * (n - 1) * (n * 2 - 1) / 6;
        for (int x : nums) {
            a += x;
            b += x * x;
        }
        int x = (a - (int) Math.sqrt(b * 2 - a * a)) / 2;
        return new int[]{x, a - x};
    }
}

###cpp

class Solution {
public:
    vector<int> getSneakyNumbers(vector<int>& nums) {
        int n = nums.size() - 2;
        int a = -n * (n - 1) / 2;
        int b = -n * (n - 1) * (n * 2 - 1) / 6;
        for (int x : nums) {
            a += x;
            b += x * x;
        }
        int x = (a - (int) sqrt(b * 2 - a * a)) / 2;
        return {x, a - x};
    }
};

###go

func getSneakyNumbers(nums []int) []int {
n := len(nums) - 2
a := -n * (n - 1) / 2
b := -n * (n - 1) * (n*2 - 1) / 6
for _, x := range nums {
a += x
b += x * x
}
x := (a - int(math.Sqrt(float64(b*2-a*a)))) / 2
return []int{x, a - x}
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

[Python3/Java/C++/Go/TypeScript] 一题一解:逐行统计(清晰题解)

方法一:逐行统计

我们可以逐行统计每行的安全设备数量,如果当前行没有安全设备,直接跳过,否则我们将当前行的安全设备数量乘以前一行的安全设备数量,累加到答案中。然后更新前一行的安全设备数量为当前行的安全设备数量。

###python

class Solution:
    def numberOfBeams(self, bank: List[str]) -> int:
        ans = pre = 0
        for row in bank:
            if (cur := row.count("1")) > 0:
                ans += pre * cur
                pre = cur
        return ans

###java

class Solution {
    public int numberOfBeams(String[] bank) {
        int ans = 0, pre = 0;
        for (String row : bank) {
            int cur = 0;
            for (int i = 0; i < row.length(); ++i) {
                cur += row.charAt(i) - '0';
            }
            if (cur > 0) {
                ans += pre * cur;
                pre = cur;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int numberOfBeams(vector<string>& bank) {
        int ans = 0, pre = 0;
        for (auto& row : bank) {
            int cur = count(row.begin(), row.end(), '1');
            if (cur) {
                ans += pre * cur;
                pre = cur;
            }
        }
        return ans;
    }
};

###go

func numberOfBeams(bank []string) (ans int) {
pre := 0
for _, row := range bank {
if cur := strings.Count(row, "1"); cur > 0 {
ans += pre * cur
pre = cur
}
}
return
}

###ts

function numberOfBeams(bank: string[]): number {
    let [ans, pre] = [0, 0];
    for (const row of bank) {
        const cur = row.split('1').length - 1;
        if (cur) {
            ans += pre * cur;
            pre = cur;
        }
    }
    return ans;
}

###rust

impl Solution {
    pub fn number_of_beams(bank: Vec<String>) -> i32 {
        let mut ans = 0;
        let mut pre = 0;
        for row in bank {
            let cur = row.chars().filter(|&c| c == '1').count() as i32;
            if cur > 0 {
                ans += pre * cur;
                pre = cur;
            }
        }
        ans
    }
}

###c

int numberOfBeams(char** bank, int bankSize) {
    int ans = 0, pre = 0;
    for (int i = 0; i < bankSize; ++i) {
        int cur = 0;
        for (int j = 0; bank[i][j] != '\0'; ++j) {
            if (bank[i][j] == '1') {
                cur++;
            }
        }
        if (cur) {
            ans += pre * cur;
            pre = cur;
        }
    }
    return ans;
}

时间复杂度 $O(m \times n)$,其中 $m$ 和 $n$ 分别为行数和列数。空间复杂度 $O(1)$。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈 😄~

每日一题-银行中的激光束数量🟡

银行内部的防盗安全装置已经激活。给你一个下标从 0 开始的二进制字符串数组 bank ,表示银行的平面图,这是一个大小为 m x n 的二维矩阵。 bank[i] 表示第 i 行的设备分布,由若干 '0' 和若干 '1' 组成。'0' 表示单元格是空的,而 '1' 表示单元格有一个安全设备。

对任意两个安全设备而言,如果同时 满足下面两个条件,则二者之间存在 一个 激光束:

  • 两个设备位于两个 不同行r1r2 ,其中 r1 < r2
  • 满足 r1 < i < r2 的 所有 行 i ,都 没有安全设备

激光束是独立的,也就是说,一个激光束既不会干扰另一个激光束,也不会与另一个激光束合并成一束。

返回银行中激光束的总数量。

 

示例 1:

输入:bank = ["011001","000000","010100","001000"]
输出:8
解释:在下面每组设备对之间,存在一条激光束。总共是 8 条激光束:
 * bank[0][1] -- bank[2][1]
 * bank[0][1] -- bank[2][3]
 * bank[0][2] -- bank[2][1]
 * bank[0][2] -- bank[2][3]
 * bank[0][5] -- bank[2][1]
 * bank[0][5] -- bank[2][3]
 * bank[2][1] -- bank[3][2]
 * bank[2][3] -- bank[3][2]
注意,第 0 行和第 3 行上的设备之间不存在激光束。
这是因为第 2 行存在安全设备,这不满足第 2 个条件。

示例 2:

输入:bank = ["000","111","000"]
输出:0
解释:不存在两个位于不同行的设备

 

提示:

  • m == bank.length
  • n == bank[i].length
  • 1 <= m, n <= 500
  • bank[i][j]'0''1'

【C语言】2125. 银行中的激光束数量

int getNum(char *row) {
    int sum = 0, len = strlen(row);
    for (int i = 0; i < len; i++) {
        sum += (row[i] - '0');
    }
    return sum;
}

int numberOfBeams(char ** bank, int bankSize){
    int res = 0, lastCnt = 0;
    for (int i = 0; i < bankSize; i++) {
        int cnt = getNum(bank[i]);
        if (cnt != 0) {
            res += lastCnt * cnt;
            lastCnt = cnt;
        }
    }
    return res;
}

微信截图_20220206001610.png

【一次遍历,统计每行数量】JavaScript

解题思路

  • 遍历每行,统计每行的数量
  • 若当前行没设备,直接跳过,进入下一行
  • 两行之间的激光数量为两行数量乘积
  • 注意第一行的处理

代码

###javascript

const numberOfBeams = bank => {
    let res = 0;
    let pre = 0;
    let cur = 0;
    for (let i = 0; i < bank.length; i++) {
        const item = bank[i];
        // 统计当前行,安全设备的数量
        cur = 0;
        for (let i = 0; i < item.length; i++) {
            if (item[i] === '1') cur++;
        }
        // 当前行没设备,直接跳过
        if (cur === 0) continue;
        // 更新res
        if (pre > 0) {
            res += pre * cur;
        }
        pre = cur;
    }
    return res;
};

阅读理解(Python/Java/C++/C/Go/JS/Rust)

题意:去掉没有安全设备的行,计算相邻行之间的激光束的数量之和。

lc2125.jpg{:width=250px}

示例 1 有 $4$ 行,安全设备的个数分别为 $3,0,2,1$。

去掉没有安全设备的行,剩下 $3,2,1$。

计算相邻行之间的激光束的数量之和,即 $3\times 2+ 2\times 1 = 8$。

class Solution:
    def numberOfBeams(self, bank: List[str]) -> int:
        ans = pre_cnt = 0
        for row in bank:
            cnt = row.count('1')
            if cnt > 0:
                ans += pre_cnt * cnt
                pre_cnt = cnt
        return ans
class Solution {
    public int numberOfBeams(String[] bank) {
        int ans = 0;
        int preCnt = 0;
        for (String row : bank) {
            int cnt = 0;
            for (char ch : row.toCharArray()) {
                cnt += ch - '0';
            }
            if (cnt > 0) {
                ans += preCnt * cnt;
                preCnt = cnt;
            }
        }
        return ans;
    }
}
class Solution {
public:
    int numberOfBeams(vector<string>& bank) {
        int ans = 0, pre_cnt = 0;
        for (auto& row : bank) {
            int cnt = ranges::count(row, '1');
            if (cnt > 0) {
                ans += pre_cnt * cnt;
                pre_cnt = cnt;
            }
        }
        return ans;
    }
};
int numberOfBeams(char **bank, int bankSize) {
    int ans = 0, pre_cnt = 0;
    for (int i = 0; i < bankSize; i++) {
        char* row = bank[i];
        int cnt = 0;
        for (int j = 0; row[j]; j++) {
            cnt += row[j] - '0';
        }
        if (cnt > 0) {
            ans += pre_cnt * cnt;
            pre_cnt = cnt;
        }
    }
    return ans;
}
func numberOfBeams(bank []string) (ans int) {
preCnt := 0
for _, row := range bank {
cnt := strings.Count(row, "1")
if cnt > 0 {
ans += preCnt * cnt
preCnt = cnt
}
}
return
}
var numberOfBeams = function(bank) {
    let ans = 0, preCnt = 0;
    for (const row of bank) {
        const cnt = row.split('1').length - 1;
        if (cnt > 0) {
            ans += preCnt * cnt;
            preCnt = cnt;
        }
    }
    return ans;
};
impl Solution {
    pub fn number_of_beams(bank: Vec<String>) -> i32 {
        let mut ans = 0;
        let mut pre_cnt = 0;
        for row in bank {
            let cnt = row.bytes().filter(|&c| c == b'1').count() as i32;
            if cnt > 0 {
                ans += pre_cnt * cnt;
                pre_cnt = cnt;
            }
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别是 $\textit{bank}$ 的行数和列数。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

❌