LeetCode、CodeForces题解2021-06-19


一、LeetCode每日一题

题目链接

串联字符串的最大长度

题目大意

给定一个字符串数组 a r r arr arr,字符串 s s s是将 a r r arr arr某一子序列字符串连接所得的字符串,如果 s s s中的每一个字符都只出现过一次,那么它就是一个可行解。

请返回所有可行解 s s s中最长长度。

题解

观察数据范围 1 < = a r r . l e n g t h < = 16 1 <= arr.length <= 16 1<=arr.length<=16,提示我们可以设计最坏 O ( 2 n ) O(2^n) O(2n)的算法。

  • 错误解法(排序+贪心)

    • 思路如下,首先直觉告诉我们如果单个字符串越长,就越有可能给答案贡献越多的字符。
    • 那么我们只要按照字符串的长度逆序排序后,贪心的去尝试选每个字符串即可(能选则选)。
    • 其实这样的决策是错误的。例如: [ " a b c d " , " a b c " , " d e f " ] ["abcd", "abc", "def"] ["abcd","abc","def"],这样的话我们的贪心决策总是会把 " a b c d " "abcd" "abcd"这个字符串选走,而后边的字符串都无法选择。导致错过 " a b c " , " d e f " "abc","def" "abc","def"这个最优解。
  • 正确解法一(二进制枚举)

    • 因为数据范围很小,我们可以枚举所有的选择的可能性,也就是数组长度 n n n的幂集,而幂集最多只有 2 16 = 65536 2^{16}=65536 216=65536种可能,所以完全不用担心 T L E TLE TLE
    • 下边介绍啥叫二进制枚举
      • 众所周知一个每一个十进制数都有且仅有唯一的二进制数与之对应。
      • 例如: 3 3 3的二进制就是 11 1 b 111_b 111b, 4 4 4的二进制是 10 0 b 100_b 100b
      • 那么我们可以刚好利用每一个二进制数的数位,因为这一位不是 0 0 0就是 1 1 1,我们用 1 1 1代表选择对应下标的字符, 0 0 0代表不选。例如: 011011 0 b 0110110_b 0110110b,这个二进制数就代表选择 1 、 2 、 4 、 5 1、2、4、5 1245下标的字符串,而不选 0 、 3 、 6 0、3、6 036下标的字符串。
      • 那么我们要枚举的范围是多少呢?
        • 显然是从 0000..0 0 b 0000..00_b 0000..00b 111...1 1 b 111...11_b 111...11b,一堆 0 0 0代表全不选,一堆 1 1 1代表全选。
        • 那这个 111...1 1 b 111...11_b 111...11b对应的十进制数是多少呢?
        • 对,没错是 2 n − 1 2^n - 1 2n1,因为你的数组长度 n n n的每一位都有选与不选两种状态,如果全选就是 [ 0 , n − 1 ] [0,n-1] [0,n1]位上每一位都是1。
        • 所以枚举的范围也就是 [ 0 , 2 n ) [0,2^n) [0,2n)
    • 会二进制枚举了,要怎么检查呢?我们仍然可以用一个 26 26 26位的二进制数去表示 [ a , z ] [a,z] [a,z] 26 26 26个字母有没有被选到,最后只要统计这个二进制数中的 1 1 1的个数即可。
      • 最后再发一个小技巧。其实我们可以一步操做找到当前二进制数对应的最后一个 1 1 1的值这个操做叫做 l o w b i t ( x ) = x & − x lowbit(x)=x \& -x lowbit(x)=x&x
      • 例如: l o w b i t ( 20 ) lowbit(20) lowbit(20),就是找到 20 20 20对应二进制 1010 0 b 10100_b 10100b,的最低位 1 1 1所对应的值,也就是 10 0 b = 8 100_b=8 100b=8
	public int maxLength(List<String> arr) {
        int n = arr.size();
        int lim = 1 << n;
        int ans = 0;
        // 枚举1 - (2^n -1),因为0000..0就代表啥都不选,所以不用考虑
        for (int i = 1; i < lim; i++) ans = Math.max(get(arr, i), ans);
        return ans;
    }

    private int get(List<String> arr, int state) {
    	// 26位的二进制数
        int used = 0;
        // 枚举每一位
        int j = 0;
        while (true) {
            int curPos = 1 << j;
            // 如果当前的1的位置超过了state的左边界
            if (curPos > state) break;
            // 如果state第j位上是1,也就代表要选对应的字符串
            if ((state & curPos) > 0) {
                char[] cur = arr.get(j).toCharArray();
                for (char c : cur) {
                    int ope = 1 << (c - 'a');
                    // 如果这个字母被占用了,就返回这是种错误的情况
                    if ((used & ope) > 0) return -1;
                    used |= ope;
                }
            }
            j++;
        }
        // popCount();
        int ret = 0;
        // 看看当前数字能消去几次1,也就代表这个数字由多少个1构成
        while (used > 0) {
            ret++;
            used -= (used & -used);
        }
        return ret;
    }
  • 递归版本
	int ans = 0, n;
    public int maxLength(List<String> arr) {
        n = arr.size();
        dfs(arr, 0, 0);
        return ans;
    }

    private void dfs(List<String> arr, int cur, int alpha) {
        if (cur == n) {
            ans = Math.max(ans, popCount(alpha));
            return;
        }
        // 不选当前的
        dfs(arr, cur + 1, alpha);
        // 选当前的字符串
        char[] s = arr.get(cur).toCharArray();
        for (char c : s) {
            int bit = 1 << (c - 'a');
            // 这一个字母被占用了,直接返回
            if ((alpha & bit) != 0) return;
            // 该位置为1
            alpha |= bit;
        }
        dfs(arr, cur + 1, alpha);
    }

    private int popCount(int v) {
        int ret = 0;
        while (v > 0) {
            v -= (v & -v);
            ret++;
        }
        return ret;
    }

时空复杂度分析

  • 非递归版本
    • 时间复杂度 O ( 2 n ∗ ∣ Σ S ∣ ) O(2^n*|\Sigma S|) O(2nΣS)
    • 只使用了常数个变量 O ( 1 ) O(1) O(1)
  • 递归版本
    • 时间复杂度 O ( 2 n ∗ ∣ Σ S ∣ ) O(2^n*|\Sigma S|) O(2nΣS) ,但是因为我们及时剪枝 ,所以复杂度应该更小,且实际测试更快。
    • 递归的调用栈占用 O ( 2 n ) O(2^n) O(2n)

二、CodeForces题解

1. Arithmetic Array

题目

Arithmetic Array

题目大意

给你 n n n个数字,输出至少还需添加几个 ≥ 0 \geq 0 0的数使得这些数的算数平均数为 1 1 1

  • 算数平均数: a 1 + a 2 + a 3 + . . . + a i i \frac{a_1 + a_2 + a_3 +...+a_i}{i} ia1+a2+a3+...+ai
  • 例子: [ 1 , 2 ] [1,2] [1,2],至少还要添加一个0,使得 1 + 2 + 0 3 = 1 \frac{1+2+0}{3}=1 31+2+0=1
    题解

观察数据范围 1 < = n < = 50 1<=n<=50 1<=n<=50 − 1 0 4 ≤ a i ≤ 1 0 4 -10^4\leq a_i \leq 10^4 104ai104 提示我们可以设计一个最坏是 O ( n 4 ) O(n^4) O(n4)的算法。

  • 感觉是分类讨论的问题,所以就讨论即可。
  • a 1 + a 2 + a 3 + . . . + a i a_1 + a_2 + a_3 +...+a_i a1+a2+a3+...+ai记作 s u m sum sum
  • 如果 s u m < 0 sum < 0 sum<0,那么我们只要添加一个 − s u m + n + 1 -sum + n + 1 sum+n+1即可,所以答案是 1 1 1
  • 如果 0 ≤ s u m < n 0\leq sum < n 0sum<n, 那么我们添加一个 n − s u m + 1 n - sum + 1 nsum+1即可,所以答案是 1 1 1
  • 如果 s u m = = n sum == n sum==n,答案是 0 0 0
  • 如果 s u m > n sum>n sum>n,那么我们可以添加一堆 0 0 0直到 s u m = = n sum==n sum==n,所以答案是 s u m − n sum-n sumn
#include <bits/stdc++.h>
using namespace std;

void solve() {
    int n;
    cin >> n;
    int sum = 0;
    for (int i = 0, v; i < n; ++i) {
        cin >> v;
        sum += v;
    }
    if (sum < 0) {
        cout << 1 << endl;
    } else if (sum == n) {
        cout << 0 << endl;
    } else cout << max(sum - n, 1) << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--) solve();
    return 0;
}

时空复杂度分析

  • 读入数据是 O ( n ) O(n) O(n)的,而输出结果为 O ( 1 ) O(1) O(1),所以总的时间复杂度为 O ( n ) O(n) O(n)
  • 只用了常数个变量,总空间复杂度 O ( 1 ) O(1) O(1)

2. Bad Boy

题目

Bad Boy

题目大意

给你 n 、 m n、m nm代表 n ∗ m n*m nm的网格,再给你一个起点 s = ( i , j ) s=(i,j) s=(i,j),请你选出两个点,使得从起点 s s s出发经过这两点并回到 s s s的路径最长。(你总会走最优的路径)。

题解

观察数据范围 1 ≤ n , m ≤ 1 0 9 , 1 ≤ i ≤ n , 1 ≤ j ≤ m 1≤n,m≤10^9, 1≤i≤n, 1≤j≤m 1n,m109,1in,1jm 提示我们可以设计一个最坏是 O ( log ⁡ n ) O(\log n) O(logn)的算法。

  • 首先,日常生活中的经验告诉我们,这两点肯定得放的尽可能的远,那么这两点应该是在对角线上。
  • 其次,这两点形成的连线应该同起点尽可能远。
  • 所以分类即可: a , b a,b a,b代表选择的两个点,红色方框代表 s s s所在的位置.
    选择的点位
#include <bits/stdc++.h>
using namespace std;

void solve() {
    int n, m, i, j;
    cin >> n >> m >> i >> j;
    if (i <= n / 2) {
        if (j <= m / 2) printf("1 %d %d 1\n", m, n);
        else printf("1 1 %d %d\n", n, m);
    } else {
        if (j <= m / 2) printf("1 1 %d %d\n", n, m);
        else printf("%d 1 1 %d\n", n, m);
    }
}

int main() {
    int t;
    cin >> t;
    while (t--) solve();
    return 0;
}

时空复杂度分析

  • 输出结果为 O ( 1 ) O(1) O(1),所以总的时间复杂度为 O ( 1 ) O(1) O(1)
  • 只用了常数个变量,总空间复杂度 O ( 1 ) O(1) O(1)
今天先写这么多吧,明天再补上CF后面的几道题。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值