首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C++进阶高级练习试题

C++进阶高级练习试题

作者头像
AI拉呱
发布2021-01-14 10:54:04
发布2021-01-14 10:54:04
1.4K0
举报

文章目录

输入不说明有多少个 Input,以 EOF 为结束标志

C

代码语言:javascript
复制
int a, b;
// scanf 返回值为变量的个数,如果没有返回 -1,EOF 是一个预定义的常量 -1
while (scanf("%d %d", &a, &b) != EOF) {  
    // ...
}

C++

代码语言:javascript
复制
int a, b;
while (cin >> a >> b) {
    // ...
}

输入不说明有多少个 Input,以某个特殊输入为结束标志

C

代码语言:javascript
复制
// 示例 1
int a, b;
while (scanf("%d %d", &a, &b) != EOF && (a != 0 && b != 0)) {
    // ...
}

// 或者
while (scanf("%d %d", &a, &b) != EOF && (a || b)) {
    // ...
}

// 示例 2
int n;
while (scanf("%d", &n) != EOF && n != 0) {
    // ...
}

C++

代码语言:javascript
复制
// 示例 1
int a, b;
while (cin >> a >> b) {
    if (a == 0 && b == 0)
        break;
    // ...
}

// 示例 2
int n;
while (cin >> n && n != 0) {
    // ...
}

指示有 N 个 Input

C

代码语言:javascript
复制
int n;
scanf("%d", &n);

int a, b;
for (int i = 0; i < n; i++) {
    scanf("%d %d", &a, &b);
    // ...
}

C++

代码语言:javascript
复制
int n;
cin >> n;

int a, b;
while(n--) {
    cin >> a >> b;
}

Python3

代码语言:javascript
复制
n = int(input())
for _ in range(n):
    # ...

指示有 N 组输入,并以某个特殊输入退出

C/C++

代码语言:javascript
复制
int n;
while (cin >> n && n != 0) {
    int a, b;
    for (int i = 0; i < n; i++) {
        cin >> a >> b;
        // ...
    }
}

输入是一整行(包括空格)

用 char[] 接收(C/C++)

代码语言:javascript
复制
const int MAXN = 1000;
char buff[MAXN];

// C
gets(buff);
puts(buff); // 输出

// C++
cin.getline(buff, MAXN);  // 第三个参数默认是 '\n'
cin.getline(buff, MAXN, '\n');

用 string 接收(C++)

代码语言:javascript
复制
string s;
getline(cin, s);          // 第三个参数默认是 '\n'
getline(cin, s, '\n');

输入是多行(包括空格)

C++

代码语言:javascript
复制
int n;
cin >> n;
cin.get();  // 否则,n 也会计入下面的 getline(),导致少一组数据

while (n--) {
    string s;
    getline(cin, s);
}

从文件读取

C

代码语言:javascript
复制
FILE *cfin = fopen("in.txt", "r");
FILE *cfout = fopen("out.txt", "w");

int a, b;
// 注意要传入文件指针
while (fscanf(cfin, "%d %d", &a, &b) != EOF) { // 类似的,把 scanf 替换成 fscanf
    fprintf(cfout, "%d\n", a + b);             // 把 printf 替换为 fprintf
}

fclose(cfin);
fclose(cfout);

C++

代码语言:javascript
复制
ifstream fin("in.txt");
ofstream fout("out.txt");

int a, b;
while (fin >> a >> b) {
    fout << a + b << endl;
}


fin.close();
fout.close();

算法排列

排列

下一个排列

LeetCode - 31. 下一个排列

题目描述

代码语言:javascript
复制
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

思路

  • 相邻的两个排列有最长公共前缀,然后找到需要交换的高位低位
  • 根据字典序的定义,依照如下步骤寻找下一个排列 字典序

从后往前找需要改变的 hi,即元素的位置

从后往前找需要交换的 lo,即 nums[hi] 的位置

交换 nums[lo] 与 nums[hi]

反转

代码语言:javascript
复制
1 5 8 5 7 6 4 3 1
        ↓ ↓ ↓ ↓ ↓
1 5 8 5 1 3 4 6 7
      ↑     ↑
      hi    lo     (hi 位置不变)

C++

代码语言:javascript
复制
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size();
        if (n <= 1) return;
    
        int hi = n - 2;
        // 1. 从后往前找需要改变的**高位** hi,即第一个降序元素的位置
        while (hi >= 0 && nums[hi + 1] <= nums[hi])
            hi--;

        if (hi >= 0) {
            // 2. 从后往前找需要交换的**低位** lo,即第一个大于 nums[hi] 的位置
            int lo = n - 1;
            while (lo >= 0 && nums[lo] <= nums[hi])
                lo--;
            // 3. 交换 nums[lo] 与 nums[hi]
            swap(nums[hi], nums[lo]);
        }

        // 4. 反转 hi 之后的序列,即 nums[hi+1: n)
        reverse(nums.begin() + hi + 1, nums.end());
        // 当 i == -1 时,该操作会使序列从字典序最大转为最小,这与 STL 中提供的 next_permutation 略有不同
    }
};

上一个排列

LintCode - 51. 上一个排列

问题描述

代码语言:javascript
复制
给定一个整数数组来表示排列,找出其上一个排列。
排列中可能包含重复的整数

样例
给出排列[1,3,2,3],其上一个排列是[1,2,3,3]

给出排列[1,2,3,4],其上一个排列是[4,3,2,1]

思路

  1. 从右往左找第一个升序的位置 hi
  2. 从右往左找第一个小于 nums[hi] 的位置 lo
  3. 交换 nums[lo] 和 nums[hi]
  4. 反转 hi 之后的位置

C++

代码语言:javascript
复制
class Solution {
public:
    /*
    * @param nums: A list of integers
    * @return: A list of integers that's previous permuation
    */
    vector<int> previousPermuation(vector<int> &nums) {
        int n = nums.size();

        if (n <= 1) return nums;

        int hi = n - 2;
        // 1. 从右往左找**第一个升序**的位置 hi
        while (hi >= 0 && nums[hi] <= nums[hi + 1])
            hi--;

        if (hi >= 0) {
            int lo = n - 1;
            // 2. 从右往左找**第一个小于** nums[hi] 的位置 lo
            while (lo >= 0 && nums[lo] >= nums[hi])
                lo--;
            // 3. 交换 nums[lo] 和 nums[hi]
            swap(nums[lo], nums[hi]);
        }

        // 4. 反转 hi 之后的位置
        reverse(nums.begin() + hi + 1, nums.end());

        return nums;  // 注意这里要你返回一个值
    }
};

STL 提供的实现(下一个排列、上一个排列) TODO

STL 提供了两个函数用于生成排列

这两个函数均以比较函数 为基础生成下一个或上一个排列

因此在使用这两个函数前,需要先对原序列进行

C++

代码语言:javascript
复制

第 k 个排列

LeetCode - 60. 第k个排列

问题描述

代码语言:javascript
复制
给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"

给定 n 和 k,返回第 k 个排列。

说明:
  给定 n 的范围是 [1, 9]。
  给定 k 的范围是 [1, n!]。
示例 1:
  输入: n = 3, k = 3
  输出: "213"
示例 2:
  输入: n = 4, k = 9
  输出: "2314"

思路

因为字典序的性质,实际不需要求出前 k-1 个序列

整体思路有点像

以 为例,找出其中第 个序列

C++

代码语言:javascript
复制
class Solution {
public:
    string getPermutation(int n, int k) {

        // nums: {1, 2, 3, ..., n}
        // 换成其他字符,按字典序存放到对应位置即可
        vector<int> nums(n + 1, 0);
        for (int i = 0; i < n; i++) // 注意:桶的下标是从 0 开始的
            nums[i] = i + 1;

        // dp: {0!=1, 1!, 2!, ..., n!}
        vector<int> dp(n + 1, 1);  // 根据上面的推导,dp[0]=1 正好可以处理最后一轮
        for (int i = 1; i <= n; i++)
            dp[i] = dp[i - 1] * i;

        k--;
        stringstream ss;
        for (int i = 1; i <= n; i++) {  // 从 1 开始
            int index = k / dp[n - i];  // 实际上没有用到 dp[n] = n!
            ss << nums[index];
            nums.erase(nums.begin() + index);  // 注意,每轮删除已处理的元素
            k = k % dp[n - i];
        }

        return ss.str();
    }
};

全排列(无重复)

LeetCode 46. 全排列

题目描述

代码语言:javascript
复制
给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:

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

思路 1

  • 利用下一个排列,先对数组排序,然后不断生成下一个排列

思路 2

  • 深度优先搜索
  • 易知,当序列中的元素不重复时,存在 n! 种不同的排列;
  • 考虑第一个位置,有 n 种可能
  • 当选定了第一个位置,第二个位置有 n-1 种可能
  • 因为每次搜索的状态数是递减的,所以这里的 dfs 是一个循环递归的过程
基于插入的写法
  • 代码量多一点,但比较好理解
代码语言:javascript
复制
class Solution {
    vector<vector<int> > ret;
    vector<int> tmp;
    vector<bool> used;
    int n = 0;

    void dfs(vector<int>& nums, int step) {
        if (tmp.size() == n) {
            ret.push_back(tmp);
            return;
        }

        for (int i = 0; i < n; i++) {   // i 每次从 0 开始,因为元素可以重复使用
            if (used[i]) continue;      // 但是在每一轮中,如果用过了需要跳过
                                        // 每一轮指的是生成一次排列的过程
            used[i] = 1;            // 标记使用
            tmp.push_back(nums[i]);
            dfs(nums, step + 1);
            tmp.pop_back();         // 回溯
            used[i] = 0;
        }
    }

public:
    vector<vector<int> > permute(vector<int>& nums) {
        n = nums.size();
        used.resize(n, 0);

        dfs(nums, 0);
        return ret;
    }
};

【注】关于 for(i=0;..)for(i=step;..) 的说明

基于交换的写法
  • 基于交换的写法,代码比较简洁,但个人认为有一点不好理解
代码语言:javascript
复制
class Solution {
    vector<vector<int> > ret;

    //void dfs(vector<int> nums, int step) {  // 值传递
    void dfs(vector<int>& nums, int step) {   // 引用传递
        if (step >= nums.size()) {
            ret.push_back(nums);
            return;
        }

        for (int i = step; i < nums.size(); i++) { // 注意:这里 i 从 step 开始
            swap(nums[step], nums[i]);
            dfs(nums, step + 1);
            swap(nums[step], nums[i]);  // 如果 nums 是值传入,则不需要这步;否则不能省略
        }
    }

public:
    vector<vector<int> > permute(vector<int>& nums) {
        dfs(nums, 0);
        return ret;
    }
};

全排列(有重复)

LeetCode - 47. 全排列 II

题目描述

代码语言:javascript
复制
给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2]
输出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

思路 1

  • 使用无重复时的方法,用 set 剔除重复(不推荐)

思路 2

  • 先对原序列排序,使相同的元素相邻;此时只处理第一个相同元素,其余跳过;
基于插入的写法
代码语言:javascript
复制
class Solution {
    vector<vector<int> > ret;
    vector<int> tmp;
    vector<bool> used;
    int n = 0;

    void dfs(vector<int>& nums, int step) {
        if (tmp.size() == n) {
            ret.push_back(tmp);
            return;
        }

        for (int i = 0; i < n; i++) {
            if (used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]))
                continue;  // 这里的 !used[i - 1] 稍难理解,可以配合 IDE 或者手推一下整个过程

            used[i] = 1;
            tmp.push_back(nums[i]);
            dfs(nums, step + 1);
            tmp.pop_back();
            used[i] = 0;
        }
    }

public:
    vector<vector<int> > permuteUnique(vector<int>& nums) {
        n = nums.size();
        used.resize(n, 0);
        sort(nums.begin(), nums.end());

        dfs(nums, 0);
        return ret;
    }
};
基于交换的写法
代码语言:javascript
复制
class Solution {
    vector<vector<int> > ret;

    //void dfs(vector<int>& nums, int step) { // 传引用无法得出正确结果
    void dfs(vector<int> nums, int step) {    // 注意这里应该使用**值传递**
        int n = nums.size();
        if (step >= n - 1) {
            ret.push_back(nums);
            return;
        }

        for (int i = step; i < n; i++) {
            if (i != step && nums[i] == nums[step])
                continue;

            swap(nums[i], nums[step]);
            dfs(nums, step + 1);
            //swap(nums[i], nums[step]); // 传引用配合回溯无法得出正确结果,
                                         // 原因在于此时会破坏剩余数组的有序性
        }
    }
public:
    vector<vector<int> > permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        dfs(nums, 0);

        return ret;
    }
};

【注】全排序的时间复杂度

  • 不重复情况下,n 个元素的不同全排列为 n! 个,所以算法的时间复杂度至少为 O(N!)
  • 因此,全排列算法对大型的数据是无法处理的

组合

组合(n 选 k,无重复)

LeetCode - 77. 组合

问题描述

代码语言:javascript
复制
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2
输出:
  [
    [2,4],
    [3,4],
    [2,3],
    [1,2],
    [1,3],
    [1,4],
  ]

思路

C++

代码语言:javascript
复制
class Solution {
    vector<vector<int> > ret;
    vector<int> tmp;   // 保存中间结果
    int K;

    void dfs(vector<int>& nums, int step) {
        if (tmp.size() >= K) {
            ret.push_back(tmp);
            return;
        }

        for (int i = step; i < nums.size(); i++) {
            tmp.push_back(nums[i]);  // nums[i] == i,所以这里直接 push(i) 也可以
            dfs(nums, i + 1);
            tmp.pop_back();
        }
    }

public:
    vector<vector<int> > combine(int n, int k) {

        K = k;
        vector<int> nums;
        for (int i = 0; i < n; i++)
            nums.push_back(i + 1);

        dfs(nums, 0);
        return ret;
    }
};

组合(n 选 k,有重复)

(未验证)

  • 如果要求每个组合中不重复,则可以先去重,再按照无重复的做法
  • 如果不要求去重,则直接按照无重复的做法即可

组合总和(数字不重复但可重复使用)

LeetCode - 39. 组合总和

思路

  • 深度优先搜索
  • 关键在于每个数字可以重复使用

C++

代码语言:javascript
复制
class Solution {
    vector<vector<int> > ret;
    vector<int> tmp;
    int cur = 0;
    int target = 0;

    void dfs(vector<int>& nums, int step) {
        if (cur >= target) {
            if (cur == target)
                ret.push_back(tmp);
            return;
        }

        for (int i = step; i < nums.size(); i++) {
            cur += nums[i];
            tmp.push_back(nums[i]);
            dfs(nums, i);  // 因为每个数组可以重复使用,所以是 dfs(i) 而不是 dfs(i+1)
            cur -= nums[i];
            tmp.pop_back();
        }
    }

public:
    vector<vector<int> > combinationSum(vector<int>& candidates, int target) {
        this->target = target;

        //sort(candidates.begin(), candidates.end()); // 不需要
        dfs(candidates, 0);

        return ret;
    }
};

【注】关于 dfs(step+1)dfs(i+1)dfs(i) 的说明

组合总和 2(存在重复数字但每个数字只能使用一次)

LeetCode - 40. 组合总和 II

思路

  • DFS,关键是如何去除重复情况

C++

代码语言:javascript
复制
class Solution {
    vector<vector<int> > ret;
    vector<int> tmp;
    int cur = 0;
    int target;

    void dfs(vector<int>& nums, int step) {
        if (cur >= target) {
            if (cur == target)
                ret.push_back(tmp);
            return;
        }

        for (int i = step; i < nums.size(); i++) {
            if (i > step && nums[i] == nums[i - 1]) // 代码说明 1
                continue;

            cur += nums[i];
            tmp.push_back(nums[i]);
            dfs(nums, i + 1);       // i+1 而不是 i,因为不能重复使用
            tmp.pop_back();
            cur -= nums[i];
        }
    }
public:
    vector<vector<int> > combinationSum2(vector<int>& candidates, int target) {
        this->target = target;

        sort(candidates.begin(), candidates.end());  // 因为存在重复,需要先排序
        dfs(candidates, 0);

        return ret;
    }
};

代码说明 1

代码语言:javascript
复制
if (i > step && nums[i] == nums[i - 1])
  • 以 [外链图片转存失败(img-ALhj2Fjq-1567509435788)(…/_assets/公式_20180905213339.png)] 为例
  • 这段代码实际上并不会过滤 [外链图片转存失败(img-NPDvI0lz-1567509435789)(…/_assets/公式_20180905213323.png)] ——i == step 的情况
  • 真正重复的情况是 [外链图片转存失败(img-2omS85CI-1567509435790)(…/_assets/公式_20180905213258.png)] 和 [外链图片转存失败(img-ROSXCPSQ-1567509435790)(…/_assets/公式_20180905213158.png)],而这段代码的目的是过滤 [外链图片转存失败(img-rHr5Fpr7-1567509435791)(…/_assets/公式_20180905213158.png)] ——i > step 的情况

组合总和 3(数字不重复且指定数量)

LeetCode - 216. 组合总和 III

问题描述

代码语言:javascript
复制
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。 

示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]

示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

思路

C++

代码语言:javascript
复制
class Solution {
    vector<vector<int> > ret;
    vector<int> tmp;
    int cur = 0;
    int K;

    void dfs(int n, int step) {
        if (cur >= n) {
            if (cur == n && tmp.size() == K)  // 同时满足才加入结果集
                ret.push_back(tmp);
            return;
        }

        for (int i = step; i <= 9; i++) {

            cur += i;
            tmp.push_back(i);
            dfs(n, i + 1);    // 因为数字不能重复使用,所以是 dfs(i+1)
            tmp.pop_back();
            cur -= i;
        }
    }

public:
    vector<vector<int> > combinationSum3(int k, int n) {
        K = k;

        dfs(n, 1);
        return ret;
    }
};

【说明】

字典序

  • 在处理排列问题时,通常时根据字典序来生成下一个排列
  • 在字典序中,记序列的升序为第一个排列,降序为最后一个排列

高位与低位

对序列中任意两个位置而言,靠近左侧的为,靠近右侧的为低位

生成排列的过程就是不断增大,减小的过程

关于 for(i=0;..)for(i=step;..) 的说明

for(i=0;..)used

for(i=step;..)

  • 所有组合问题

简单来说,以 为例

【注】关于 dfs(step+1)dfs(i+1)dfs(i) 的说明

(以下均为个人小结,并没有严格验证)

dfs(step+1)dfs(i+1)

简单来说, 指的是生成 序列中的第 个位置; 指的是使用 中的第 个元素

以不重复集合 为例说明:

dfs(i+1)dfs(i)

在问题中,还用到了

代码语言:javascript
复制
for (int i = step; i < nums.size(); i++) {
    // ...
    dfs(nums, i);
    // ...
}
  • 一方面,它跟组合问题类似,用过的数字不再使用;因此使用的是 i 而不是 step
  • 另一方面,每个数字可以重复使用,因此使用的是 dfs(i) 而不是 dfs(i+1)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/09/03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 输入不说明有多少个 Input,以 EOF 为结束标志
    • C
    • C++
  • 输入不说明有多少个 Input,以某个特殊输入为结束标志
    • C
    • C++
  • 指示有 N 个 Input
    • C
    • C++
    • Python3
  • 指示有 N 组输入,并以某个特殊输入退出
    • C/C++
  • 输入是一整行(包括空格)
    • 用 char[] 接收(C/C++)
    • 用 string 接收(C++)
  • 输入是多行(包括空格)
    • C++
  • 从文件读取
    • C
    • C++
  • 算法排列
    • 排列
      • 下一个排列
      • 上一个排列
      • STL 提供的实现(下一个排列、上一个排列) TODO
      • 第 k 个排列
      • 全排列(无重复)
      • 全排列(有重复)
      • 【注】全排序的时间复杂度
    • 组合
      • 组合(n 选 k,无重复)
      • 组合(n 选 k,有重复)
      • 组合总和(数字不重复但可重复使用)
      • 组合总和 2(存在重复数字但每个数字只能使用一次)
      • 组合总和 3(数字不重复且指定数量)
    • 【说明】
      • 字典序
      • 关于 for(i=0;..) 与 for(i=step;..) 的说明
      • 【注】关于 dfs(step+1)、dfs(i+1)、dfs(i) 的说明
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档