【leetcode算法面试】leetcode题目6-排列组合

题号题目说明
31Next Permutation 下一个排列 
39Combination Sum  组合之和dfs
40Combination Sum II 组合之和IIdfs
46Permutations 全排列dfs
47Permutations II 全排列IIdfs
77Combinations 组合项dfs
90Subsets II 組合之二 

 

31. [LeetCode] Next Permutation 下一个排列

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place, do not allocate extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

     找到一个数比一个数小的,然后交换,从下一个位置反转

     步骤: 后找,小大,交换,翻转

      查找字符串中最后一个不再是升序的位置  i,一直都是 nums[i] < nums[i + 1]

      查找比 i 大的最小的一个值,

      交换这俩值

      把这俩值翻转

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size(), i = n - 2, j = n - 1;
        while (i >= 0 && nums[i] >= nums[i + 1]) --i;
        if (i >= 0) {
            while (nums[j] <= nums[i]) --j;
            swap(nums[i], nums[j]);
        }
        reverse(nums.begin() + i + 1, nums.end());
    }
};

func nextPermutation(nums []int) {
	var reverse = func(nums []int, start, end int) {
		if len(nums) < end {
			return
		}
		for start < end {
			nums[start], nums[end] = nums[end], nums[start]
			start++
			end--
		}
	}
	var i int
	for i = len(nums) - 2; i >= 0; i-- {
		if nums[i] < nums[i+1] {
			for j := len(nums) - 1; j > i; j-- {
				if nums[j] > nums[i] {
					nums[i], nums[j] = nums[j], nums[i]
					break
				}
			}
			reverse(nums, i+1, len(nums)-1)
			return
		}
	}
	fmt.Printf("%v\n", i)
	reverse(nums, 0, len(nums)-1)

}

 

39. [LeetCode] Combination Sum  组合之和

      利用到递归,都是一个套路,都是需要另写一个递归函数,这里我们新加入三个变量,start记录当前的递归到的下标,out为一个解,res保存所有已经得到的解,每次调用新的递归函数时,此时的target要减去当前数组的的数

func combinationSum(candidates []int, target int) [][]int {
	var res = make([][]int, 0)
	var cur = make([]int, 0)
	dfs(candidates, target, 0, &cur, &res)
	return res
}

func dfs(candidates []int, target int, start int, cur *[]int, res *[][]int) {
	if target < 0 {return}
	if target == 0 {
		var temp = make([]int, len(*cur))
		copy(temp, *cur)
		*res = append(*res, temp)
		return
	}
	for i:=start; i<len(candidates); i++ {
		*cur = append(*cur, candidates[i])
		dfs(candidates, target-candidates[i], i, cur, res)
		*cur = (*cur)[:len(*cur)-1]
	}
}

 

40. Combination Sum II 组合之和II

   考虑到dfs要从i+1开始,但是考虑不到怎么避免C里面如果有重复元素无法被重用的问题

func combinationSum2(candidates []int, target int) [][]int {
	var res = make([][]int, 0)
	var cur = make([]int, 0)
	sort.Ints(candidates)
	dfs(candidates, target, 0, &cur, &res)
	return res
}

func dfs(candidates []int, target int, start int, cur *[]int, res *[][]int) {
	if target < 0 {return}
	if target == 0 {
		var temp = make([]int, len(*cur))
		copy(temp, *cur)
		*res = append(*res, temp)
		return
	}
	for i:=start; i<len(candidates); i++ {
		if i > start && candidates[i] == candidates[i-1] {continue}
		*cur = append(*cur, candidates[i])
		dfs(candidates, target-candidates[i], i+1, cur, res)
		*cur = (*cur)[:len(*cur)-1]
	}
}

 

46. Permutations 全排列

   题解:比如 12345

  可以逐个替换,分为  1       2345

                                     2      1345

                                     3       2145

                                     4       2315 

                                     5       2341

   右面可以看为一组,继续递归

func permute(nums []int) [][]int {
	var ret = make([][]int, 0)
	var cur, visited = make([]int, 0), make([]int, len(nums))
	dfs(nums, visited, cur, &ret)
	return ret
}

func dfs(nums []int, visited []int, cur []int, ret *[][]int) {
	if len(cur) == len(nums) {
		var temp = make([]int, len(nums))
		copy(temp, cur)
		*ret = append(*ret, temp)
		return
	}
	for i:=0; i<len(nums); i++ {
		if visited[i] == 1 {continue}
		visited[i] = 1
		cur = append(cur, nums[i])
		dfs(nums, visited, cur, ret)
		cur = (cur)[:len(cur)-1]
		visited[i] = 0
	}
}

   题解二,产生当前排列的下一个大的排列,这样就可以使用非递归完成

 

47. Permutations II 全排列II

      在递归函数中要判断前面一个数和当前的数是否相等,如果相等,且其对应的visited中的值为1,当前的数字才能使用(下文中会解释这样做的原因),否则需要跳过,这样就不会产生重复排列了

func permuteUnique(nums []int) [][]int {
	var ret = make([][]int, 0)
	var cur, visited = make([]int, 0), make([]int, len(nums))
	sort.Ints(nums)
	dfsUnique(nums, visited, cur, &ret)
	return ret
}

func dfsUnique(nums []int, visited []int, cur []int, ret *[][]int) {
	if len(cur) == len(nums) {
		var temp = make([]int, len(nums))
		copy(temp, cur)
		*ret = append(*ret, temp)
		return
	}
	for i:=0; i<len(nums); i++ {
		if i > 0 && nums[i] == nums[i-1] && visited[i-1]==1 {continue}
		if visited[i] == 1 {continue}
		visited[i] = 1
		cur = append(cur, nums[i])
		dfsUnique(nums, visited, cur, ret)
		cur = (cur)[:len(cur)-1]
		visited[i] = 0
	}
}

 

77. Combinations 组合项

func combine(n int, k int) [][]int {
	var res, cur = make([][]int, 0), make([]int, 0)
	var visited = make([]int, n+1)
	dfs(k, 1, visited, cur, &res)
	return res
}

func dfs(k, start int, visited []int, cur []int, res *[][]int) {
	if len(cur) > k {return}
	if len(cur) == k {
		temp := make([]int, k)
		copy(temp, cur)
		*res = append(*res, temp)
		return
	}
	for i:=start; i<len(visited); i++ {
		if visited[i] == 1 {continue}
		visited[i] = 1
		cur = append(cur, i)
		dfs(k, i+1, visited, cur, res)
		visited[i] = 0
		cur = cur[:len(cur)-1]
	}
}

 

90. Subsets II 組合之二

func subsetsWithDup(nums []int) [][]int {
	var res, cur = make([][]int, 0), make([]int, 0)
	sort.Ints(nums)
	dfs(nums, 0, cur, &res)
	return res
}

func dfs(nums []int, start int, cur []int, res *[][]int) {
	var temp = make([]int, len(cur))
	copy(temp, cur)
	*res = append(*res, temp)

	for i := start; i < len(nums); i++ {
		cur = append(cur, nums[i])
		dfs(nums, i+1, cur, res)
		cur = cur[:len(cur)-1]
		for i+1 < len(nums) && nums[i] == nums[i+1] {
			i++
		}
	}
}

 

一. 全排列(递归)

        比如,假设集合是{a,b,c},那么这个集合中元素的全部排列是{(a,b,c),(a,c,b),(b,a,c),(b,c,a),(c,a,b),(c,b,a)},显然,给定n个元素共同拥有n!种不同的排列,假设给定集合是{a,b,c,d},能够用以下给出的简单算法产生其全部排列,即集合(a,b,c,d)的全部排列有以下的排列组成:

     (1)以a开头后面跟着(b,c,d)的排列

    (2)以b开头后面跟着(a,c,d)的排列

    (3)以c开头后面跟着(a,b,d)的排列

    (4)以d开头后面跟着(a,b,c)的排列,这显然是一种递归的思路

    那么,这个问题可以这样来看。对

    T=【T=【x1,x1,x2,x3,x4,x5,........xn−1,xn】x2,x3,x4,x5,........xn−1,xn】
    我们获得了在第一个位置上的所有情况之后(注:是所有的情况),对每一种情况,抽去序列TT中的第一个位置,那么对于剩下的序列可以看成是一个全新的序列

    T1=【x2,x3,x4,x5,........xn−1,xn】T1=【x2,x3,x4,x5,........xn−1,xn】
序列T1T1可以认为是与之前的序列毫无关联了。同样的,我们可以对这个T1T1进行与TT相同的操作,直到TT中只一个元素为止。这样我们就获得了所有的可能性。所以很显然,这是一个递归算法。

   第一位的所有情况:无非是将x1x1与后面的所有数x2,x3,.......xnx2,x3,.......xn依次都交换一次

   示意图如下:

     参考: https://blog.csdn.net/summerxiachen/article/details/60579623

void permutation1(char* str,int sbegin,int send)    //全排列的非去重递归算法  
    {  
        if( sbegin == send) //当 sbegin = send时输出  
        {  
            for(int i = 0;i <= send; i++)   //输出一个排列  
                cout << str[i];  
            cout << endl;  
        }  
        else  
        {  
            for(int i = sbegin; i <= send; i++) //循环实现交换和sbegin + 1之后的全排列  
            {  
                swap(str[i],str[sbegin]);   //把第i个和第sbegin进行交换  
                permutation1(str,sbegin + 1,send);  
                swap(str[i],str[sbegin]);   //【注1】交换回来  
            }  
        }  
    }  

 

二. 全排列(next_permutation)

#include <stdio.h>
#include <stdlib.h>

void swapTwo(char *n1, char *n2) {
	char temp = *n1;
	*n1 = *n2;
	*n2 = temp;
}

int nextPemutation(char a[], int n) {
	int item_head;
	int item;
	for (item=n-1; item>0; item--) {
		if (a[item-1] < a[item])
			break;
	}

	if (item == 0) return -1;

	item_head = item - 1;
	for (item=n-1; item>item_head; item--) {
		if (a[item_head] < a[item])
			break;
	}
	
	swapTwo(&a[item_head], &a[item]);

	for (item=item_head+1; item<n; item++, n--) {
		swapTwo(&a[item], &a[n-1]);
	}

	return 0;
}

int main() {
	char a[] = "abcd";
	while (nextPemutation(a, 4) >= 0) {
		printf("%s\n", a);
	}
	return 0;
}

 

三. 组合 第归

a. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。

b. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。

    在n个数中,标记k个数,然后输出这k个数的过程。使用一个visited数组来记录相应下标的数是否被选中 

void dfs(int pos, int cnt, int n, int k, int a[],bool visited[]) {
    //已标记了k个数,输出结果
    if (cnt == k) {
        for (int i = 0; i < n; i++)
            if (visited[i]) cout << a[i] << ' ';
        cout << endl;
        return;
    }

    //处理到最后一个数,直接返回
    if (pos == n) return;

    //如果a[pos]没有被选中
    if (!visited[pos]) {
        //选中a[pos]
        visited[pos] = true;
        //处理在子串a[pos+1, n-1]中取出k-1个数的子问题
        dfs(pos + 1, cnt + 1, n, k, a,visited);
        //回溯
        visited[pos] = false;   
    }
    //处理在子串a[pos+1, n-1]中取出k个数的问题
    dfs(pos + 1, cnt, n, k, a, visited);
}

 

 

DFS(深度优先搜索)

       Depth-First-Search,用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最差情况算法时间复杂度为O(!n)

    参考: https://blog.csdn.net/ldx19980108/article/details/76324307

适用场景:

      有三个方面,分别是输入数据、状态转换图、求解目标;

输入数据:递归数据结构,如单链表,二叉树,集合,则百分之百可以使用深搜

代码模版

/**
     *  DFS模版
     *  @param input  输入数据指针
     *  @param path   当前路径,也是中间结果
     *  @param result 存放最终结果
     *  @param gap    标记当前位置或距离目标的距离
     *
     *  @return 路径长度,如果是路径本身,则不需要返回长度
     */
    template <typename type>
    void dfs(type & input, type & path, type & result, int cur or gap) {
        if (数据非法) return 0;  // 终止条件
        if (cur == input.size()) { // 收敛条件 (or gap == 0)
            将path放入到result中;
        }
        
        if (可以剪枝) return ;
        
        for (...) {  //执行所有可能的扩展动作
            1.执行动作,修改path
            2.dfs(input, path, result, cur + 1 or gap - 1);
            3.恢复path
        }
    }

 

 

1. 全排列模板

char  a[15];
char re[15];
int vis[15];
//假设有n个字符要排列,把他们依次放到n个箱子中
//先要检查箱子是否为空,手中还有什么字符,把他们放进并标记。
//放完一次要恢复初始状态,当到n+1个箱子时,一次排列已经结束
void dfs(int step)
{
    int i;
    if(step==n+1)//判断边界
    {
        for(i=1;i<=n;i++)
            printf("%c",re[i]);
        printf("\n");
        return ;
    }
    for(i=1;i<=n;i++)//遍历每一种情况
    {
        if(vis[i]==0)//check满足
        {
            re[step]=a[i];
            vis[i]=1;//标记
            dfs(step+1);//继续搜索
            vis[i]=0;//恢复初始状态
        }
    }
    return ;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值