【问题标题】:confused on uniqueness in permutation calculation对排列计算的唯一性感到困惑
【发布时间】:2015-11-10 08:51:31
【问题描述】:

解决以下问题作为算法难题。参考了一些类似的解决方案(并在下面发布其中一个),尝试过并且它们奏效了。问题是,对于“swap(num[i], num[k]);”这一行,我们如何确保我们总是可以交换到以前从未尝试过的数字(例如,假设我们在当前迭代中将 1 与 2 交换for 循环,那么以后我们有可能在相同级别/递归调用层的相同 for 循环的下一次迭代中将 2 换回 1)?我很困惑,因为我们通过引用传递 num,并且以后(较低级别/层)递归调用很可能会修改 num 的内容,这会导致我们已经评估过的数字交换回来。但是,我尝试过,它适用于我所有的测试用例。想知道下面的解决方案是 100% 正确,还是碰巧通过了我的测试用例? :)

这是我正在调试的详细问题陈述和代码,

给定一组可能包含重复的数字,返回所有可能的唯一排列。

例如, [1,1,2] 具有以下独特排列: [1,1,2]、[1,2,1] 和 [2,1,1]

class Solution {
public:
    void recursion(vector<int> num, int i, int j, vector<vector<int> > &res) {
        if (i == j-1) {
            res.push_back(num);
            return;
        }
        for (int k = i; k < j; k++) {
            if (i != k && num[i] == num[k]) continue;
            swap(num[i], num[k]);
            recursion(num, i+1, j, res);
        }
    }
    vector<vector<int> > permuteUnique(vector<int> &num) {
        sort(num.begin(), num.end());
        vector<vector<int> >res;
        recursion(num, 0, num.size(), res);
        return res;
    }
};

提前致谢, 林

【问题讨论】:

  • num 不是通过引用传递给recursion() - 每个调用都会获得一个全新的数字数组副本。
  • 在我看来," if (i != k && num[i] == num[k]) continue; 行创建了很多重复检查,更简单的是 if (num[i] == 数字 [k]) 继续;会更好,因为该循环中的第一个遍历不会进入递归调用,而且 int j 参数始终是 num.size 所以为简单起见,您可能希望将其删除
  • @user1316208 没有i!=k,第一次递归(没有任何改变/某些东西与自身交换)不会发生=>没有结果。 i!=k 是必需的。
  • @deviantfan 在我看来,结果将在 for 循环中进一步获得。但也许我错了
  • @user1316208 用 1 1 2 试试(手动,或代码,你喜欢什么)。不会有任何结果(但也许我也错了:))

标签: c++ algorithm


【解决方案1】:

正如@notmyfriend 在 cmets 中所说,num 实际上是复制每个函数调用。
所以现在归结为:

  • 在所有数组值中,选择一个作为第一个并将其放置在那里。
    在循环中为每个值一次,然后递归:
    • 在第一个之后的所有值中,选择一个作为第一个并将其放在那里...

...等等,结合检查以过滤掉没有任何变化的交换,即。过滤掉重复项。

如果 num 是一个真正的参考,它就不再起作用(至少在没有额外步骤的情况下不会起作用)。
例如。 1 1 2 是一个简单的反例,它会给出结果:

112, 121, 211, 112, 121  

即。尽管进行了检查,但仍有重复项(可能存在
也是根本不生成某些排列的示例)。

关于评论:

默认情况下,C++ 中的每个普通函数参数都会被复制
(正常 = 没有明确的参考符号 '&' 等)。
也许您正在考虑 C 风格的数组:本质上,传递给那里的是一个指针(指向第一个值)。指针被复制,但原始指针和复制指针都指向相同的内存位置。

虽然std::vector 的目的是(也)包含一个数组,但向量本身是一个单一的类对象(其中包含指向某处值的指针)。一个类可以自己定义它应该如何被复制(使用复制构造函数)。
从技术上讲,向量类可以将复制实现为指针复制,那么它与将整个向量作为引用传递的效果相同;但是 C++ 的创建者想要保留复制语义,即。复制容器类应该制作一个所有值都重复的真实副本。
对于非抄袭,已经有参考了……

【讨论】:

  • 感谢 deviantfan,如果它是按值复制的。然后我明白了。但是为什么它是按值复制的呢?我可能是错的,但我认为如果我们传递一个整数向量,它是通过引用传递的吗?不?更多细节表示赞赏。
  • @LinMa 查看答案中的补充。
  • 感谢 deviantfan 澄清传递对向量的引用的行为,我想知道在传递对向量的引用时是否只为整数向量或任何向量(说自定义类对象)?谢谢。
  • 一切都是按值传递的,除非你另有说明。通常,您应该始终通过引用传递类(如果您不需要修改函数中的类,则为 const 引用),除非您特别需要它的副本(例如,在这个算法中)。
【解决方案2】:

您可以在下面找到用 Java 编写的解决方案。很抱歉没有在 C++ 中提供解决方案,我已经很长时间没有使用它了。但语法会相似。

解决方案是使用回溯 (https://en.wikipedia.org/wiki/Backtracking) 另外我正在使用 hashset 来检查唯一性,可能有一个不使用任何 hashset 类型数据结构的解决方案,因为我的解决方案是使用额外的内存来提供唯一的解决方案。

样本输入输出;

input  :  [1, 1, 2]
output :  [1, 1, 2]
          [1, 2, 1]
          [2, 1, 1]

解决办法是;

public class permutation {

    public static void main(String[] args) {
        permutation p = new permutation();
        p.permute(new int[] { 1, 1, 2 });
    }

    HashSet<String> set = new HashSet<String>();

    private void permute(int[] arr) {
        set.clear();
        this.permute(arr, 0, arr.length - 1);
    }

    private void permute(int[] arr, int l, int r) {
        if (l == r) {
            String key = Arrays.toString(arr);
            if (set.contains(key))
                return;
            set.add(key);
            System.out.println(key);
        } else {
            for (int i = l; i <= r; i++) {
                swap(arr, l, i);
                permute(arr, l + 1, r);
                swap(arr, i, l);
            }
        }
    }

    private void swap(int[] arr, int l, int r) {
        int tmp = arr[l];
        arr[l] = arr[r];
        arr[r] = tmp;
    }
}

【讨论】:

  • 除了错误的语言,OP 已经有一个可行的解决方案(也比你的更好),只是不理解它。
猜你喜欢
  • 1970-01-01
  • 2015-01-21
  • 1970-01-01
  • 1970-01-01
  • 2021-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-12
相关资源
最近更新 更多