【回溯】【leetcode】构建字典序最大的可行序列

题目:

给你一个整数 n ,请你找到满足下面条件的一个序列:

  • 整数 1 在序列中只出现一次。
  • 2 到 n 之间每个整数都恰好出现两次。
  • 对于每个 2 到 n 之间的整数 i ,两个 i 之间出现的距离恰好为 i 。

序列里面两个数 a[i] 和 a[j] 之间的 距离 ,我们定义为它们下标绝对值之差 |j - i| 。

请你返回满足上述条件中 字典序最大 的序列。题目保证在给定限制条件下,一定存在解。

一个序列 a 被认为比序列 b (两者长度相同)字典序更大的条件是: a 和 b 中第一个不一样的数字处,a 序列的数字比 b 序列的数字大。比方说,[0,1,9,0] 比 [0,1,5,6] 字典序更大,因为第一个不同的位置是第三个数字,且 9 比 5 大。

示例 1:

输入:n = 3
输出:[3,1,2,3,2]
解释:[2,3,2,1,3] 也是一个可行的序列,但是 [3,1,2,3,2] 是字典序最大的序列。
示例 2:

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

提示:

  • 1 <= n <= 20

来源:

1718. 构建字典序最大的可行序列

解题思路:回溯

定义一个数组used,记录某个数字是否已使用,用于回溯。一个数组path,长度2*n-1,记录结果。递归方向与path一致,每次递归从最大的数N开始依次判断是否满足条件,保证结果字典序最大。

  • 结果满足条件:path遍历完成,每个元素都不为0。
  • 递归调用分两种:
    • 数字1:数字1只有1个,不用考虑距离
    • 数字2~N:每个数字出现2次且距离为当前数字,所以根据path[start]当前位置找出符合条件的数字,然后递归回溯。
  • start变量:递归内指向path,当path[start]已有数字时,跳过。
class Solution {
public:
    vector<int> result;
    bool ok;
    vector<int> constructDistancedSequence(int n) {
        ok = false;
        vector<int> path(n*2 - 1, 0);
        vector<bool> used(n+1, false);
        back(path, used, n, 0);
        return result;
    }
    // 选择一个数字放到path[start], path[start],path[start+i]可能已放置,需要判断
    // start从0开始直到n*2-1
    void back(vector<int>& path, vector<bool>& used, int n, int start) {
        if (start == path.size()) {
            result = path;
            ok = true;
            return;
        }
        if (path[start] > 0) {
            back(path, used, n, start+1);
            return;
        }
        // 优先选择较大的数字放到path[start]
        for (int i = n; i >= 1; i--) {
            if (ok) break;
            if (used[i]) continue;

            used[i] = true;
            if (i == 1) {
                path[start] = 1;
                back(path, used, n, start+1);
                path[start] = 0;
            } else {
                if (start + i < path.size() && path[start + i] == 0) {
                    // 满足条件的2个位置都是空
                    path[start] = i;
                    path[i+start] = i;
                    back(path, used, n, start+1);
                    path[start] = 0;
                    path[i+start] = 0;
                }
            }
            used[i] = false;
        }
    }
};

后记:

今天被这题困扰了很久,先前写的代码总是超时,下面贴出当时的代码,并指出其中的问题。

问题一:n从1开始,则需要将所有结果都判断一次才能找出字典序最大结果,而且back被循环调用,所以超时。

问题二:递归的是数字N,遍历path找出位置,但这样找出的结果不是最大的,所以应该为:递归path,遍历数字N。

class Solution {
public:
    vector<int> result;
    vector<int> constructDistancedSequence(int n) {
        vector<int> path(n*2 - 1, 0);
        result = path;
        // 将数字1放入每一个位置,然后递归+回溯
        for (int i = 0; i < path.size(); i++) {
            path[i] = 1;
            back(path, n, 2);
            path[i] = 0;
        }
        return result;
    }
    // start从2开始直到n+1
    void back(vector<int>& path, int n, int start) {
        if (start == n + 1) {
            if (greater(path, result)) {
                result = path;
            }
            return;
        }
        for (int i = 0; i < path.size(); i++) {
            if (path[i] == 0 && i+start < path.size() && path[i+start] == 0) {
                // 满足条件的2个位置都是空
                path[i] = start;
                path[i+start] = start;
                back(path, n, start+1);
                path[i] = 0;
                path[i+start] = 0;
            }
        }
    }
    bool greater(const vector<int>& a, const vector<int>& b) {
        for (int i = 0; i < a.size(); i++) {
            if (a[i] > b[i]) {
                return true;
            } else if (a[i] < b[i]) {
                return false;
            }
        }
        return false;
    }
};

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值