天天算法之回溯

天天算法之回溯

思想

  • 利用递归+栈来刻画行为逻辑,遍历问题的所有可能的解法。
  • 回溯依赖栈的方式进行诠释。

实践

    1. Next Permutation

      首先要理解题目,也就是求一组数的下一个全排列(字典顺序)。

      然后进行步骤推演,首先找到第一个正序对,如果正序对是末尾的最后两个元素,则直接交换即可。

      如果正序对不是最后两个末尾的元素的情况,则需要 在正序对终止元素后面的 倒序数列中 找到比正序对起始元素大 且在倒序数列中最小的数 并与之交换位置。然后让正序对终止元素后面的序列正序。比如:123654,第一个正序对为23,正序对终止元素为3,在倒序数列中最小的数为4(比3大),然后交换位置得到124653,再让正序对终止元素后面的序列正序,最终得到124356,即为下一个全排列序列。

      在让正序对终止元素后面的序列正序的过程中,由于正序对终止元素后面是倒序的,所以直接翻转即可,对应reverse方法。

    class Solution {
        public void nextPermutation(int[] nums) {
            if(nums.length == 1){
                return;
            }
            
            int firstOrderIndex = findFirstOrderIndex(nums);
            
            if(firstOrderIndex == nums.length - 2){
                swap(nums, firstOrderIndex, firstOrderIndex+1);
                return;
            }
            
            if(firstOrderIndex==-1){
                reverse(nums, 0, nums.length-1);
                return;
            }
            
            int minNumIndex = findMinNumThanNIndex(nums, nums[firstOrderIndex]);
            swap(nums, firstOrderIndex, minNumIndex);
            reverse(nums, firstOrderIndex+1, nums.length-1);
        }
        
        /**
         * 找到比某个数大的最小的数,在倒序序列中
         */
        private int findMinNumThanNIndex(int[] nums, int n){
            int i = nums.length-1;
            for(; i>=0; i--){
                if(nums[i]>n){
                    break;
                }
            }
            return i;
        }
        
        /**
         * 找到第一次出现正序对的数据的下标,比如:3,1,2,则返回 1。 3,2,1,则返回-1.
         */
        private int findFirstOrderIndex(int[] nums){
            int i = nums.length-2;
            for(; i>=0; i--){
                if(nums[i]<nums[i+1]){
                    break;
                }
            }
            return i;
        }
        
        /**
         * 数组值链反转
         */
        private void reverse(int[] nums, int i, int j){
            while(i<j){
                swap(nums,i,j);
                ++i;
                --j;
            }
        }
        
        /**
         * 数组值交换
         * 亦或操作:相同为0,不同为1。
         * 测试提交此种方法比正常 值值交换 快1ms左右。
         */
        private void swap(int[] nums, int i, int j){
        // 自己与自己亦或会置空。如果涉及i和j相同的情况,需要附加判断逻辑。
        // if(i == j) return;
        		// 留下 nums[i] 与 nums[j] 的不同赋值给 nums[i]
            nums[i]^=nums[j];
            // nums[j] 去掉与 nums[i] 的相同部分,剩下 nums[i] 赋值给 nums[j]
            nums[j]^=nums[i];
            // nums[i] 加上 nums[i]与nums[j] 不同的部分,变为 nums[j]
            nums[i]^=nums[j];
        }
    }
    
    1. Permutations II

      打印所有全排列序列。

    class Solution {
        public List<List<Integer>> permuteUnique(int[] nums) {
            List<List<Integer>> result = new ArrayList<List<Integer>>();
            permute(nums, 0, result);
            return result;
        }
            
        private void permute(int[] nums, int stackHeight, List<List<Integer>> result){
            if (stackHeight == nums.length) {
                // 栈高 满 则输出
                result.add(Arrays.stream(nums).boxed().collect(Collectors.toList()));
                return;
            }
          
          	// 针对题眼:nums, that might contain duplicates
            Set<Integer> set = new HashSet<>();
          
            for (int j = stackHeight; j < nums.length; ++j) {
                if (!set.contains(nums[j])) {
                    // 横向遍历中如果已经入过栈,则后续相同的数无需再入栈,避免重复。
                    swap(nums, stackHeight, j);
                    // 纵向递归
                    permute(nums, stackHeight + 1, result);
                    // 出栈
                    swap(nums, stackHeight, j);
                    set.add(nums[j]);
                }
            }
        }
        
        private void swap(int[] nums, int i, int j){
            if(i == j)return;
            nums[i] ^= nums[j];
            nums[j] ^= nums[i];
            nums[i] ^= nums[j];
        }
    }
    
  • 1850:Minimum Adjacent Swaps to Reach the Kth Smallest Number

    class Solution {
        public int getMinSwaps(String num, int k) {
            StringBuilder numNew = new StringBuilder(num);
            for(int i = 0; i < k; i++){
                getNextPermutation(numNew);
            }
            return countSwap(num, numNew);
        }
        
        private int countSwap(String num, StringBuilder numNew){
            int j = 0, count = 0;
            for(int i = 0; i < num.length() - 1; i++){
                if(num.charAt(i) != numNew.charAt(j)){
                    while(num.charAt(i) != numNew.charAt(j)){
                        ++j;
                    }
                    while(j>i){
                                swap(numNew, j-1, j);
                                --j;
                                count++;
                    }
                }
                j++;
            }
            return count;
        }
        
        private void getNextPermutation(StringBuilder numNew) {
            int firstPosPairIndex = findFirstPosPair(numNew);
            if(firstPosPairIndex == numNew.length() - 2){
                swap(numNew, numNew.length() - 2, numNew.length() - 1);
                return;
            }
    //         if(firstPosPairIndex == -1){
    // The tests are generated in such a way that kth smallest wonderful integer exists.
    //         }
            
            int smallBiggerIndexThanN = findSmallBiggerThanN(numNew, numNew.charAt(firstPosPairIndex));
            swap(numNew, firstPosPairIndex, smallBiggerIndexThanN);
            reverse(numNew, firstPosPairIndex + 1, numNew.length() -1);
        }
        
        private int findSmallBiggerThanN(StringBuilder numNew, char n){
            int i = numNew.length() - 1;
            for(; i >= 0; i--){
                if(numNew.charAt(i) > n){
                    return i;
                }
            }
            return i;
        }
        
        private int findFirstPosPair(StringBuilder numNew){
            int i = numNew.length() - 2;
            for(; i >= 0; i--){
                if(numNew.charAt(i) < numNew.charAt(i+1)){
                    return i;
                }
            }
            return i;
        }
        
        private void reverse(StringBuilder numNew, int i, int j){
            while(i < j){
                swap(numNew, i, j);
                i++;
                j--;
            }
        }
        
        private void swap(StringBuilder numNew, int i, int j){
            if(i == j) return;
            char tmp = numNew.charAt(j);
            numNew.setCharAt(j, numNew.charAt(i));
            numNew.setCharAt(i, tmp);
            // num.charAt(i) = num.charAt(j);
            // num.charAt(j) = tmp;
        }
    }
    

leetCode国际版类型题

  • \31. Next Permutation:https://leetcode.com/problems/next-permutation/
  • \46. Permutations:https://leetcode.com/problems/permutations/
  • \47. Permutations II:https://leetcode.com/problems/permutations-ii/
  • \60. Permutation Sequence:https://leetcode.com/problems/permutation-sequence/
  • \1850. Minimum Adjacent Swaps to Reach the Kth Smallest Number:https://leetcode.com/problems/minimum-adjacent-swaps-to-reach-the-kth-smallest-number/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值