Tag Archives: 递归

LeetCode Climbing Stairs

70. Climbing Stairs

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Note: Given n will be a positive integer.

Example 1:

Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps

Example 2:

Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

虽然是easy模式,但是是很好的一道题。问爬一个有n个台阶的楼梯,每次只能跨1步或者2步,共有多少种方案。 使用DP解决,假设前n个台阶共有dp[n]种方案,来了一个新台阶第n+1个台阶,那么这个台阶可以单独1步跨,则方案数为dp[n];也可以和第n个台阶合在一起一次性跨2步,则还剩n-1个台阶,所以方案数为dp[n-1]。综合起来就是dp[n+1]=dp[n]+dp[n-1],这其实就是斐波那契数列。
求斐波那契数列的第n项是一个经典问题,如果使用原始的递归求解,则会有很多重复计算,导致超时:

class Solution {
public:
    int climbStairs(int n)
    {
        if (n == 1)
            return 1;
        if (n == 2)
            return 2;
        return climbStairs(n – 1) + climbStairs(n – 2);
    }
};

但是每次我们只需要前两项的数值,所以可以优化为:

class Solution {
public:
    int climbStairs(int n)
    {
        if (n == 1)
            return 1;
        if (n == 2)
            return 2;
        int a1 = 1, a2 = 2, tmp;
        for (int i = 3; i <= n; i++) {
            tmp = a1 + a2;
            swap(a1, a2);
            swap(a2, tmp);
        }
        return a2;
    }
};

本代码提交AC,用时0MS。

LeetCode Same Tree

100. Same Tree

Given two binary trees, write a function to check if they are the same or not.

Two binary trees are considered the same if they are structurally identical and the nodes have the same value.

Example 1:

Input:     1         1
          / \       / \
         2   3     2   3

        [1,2,3],   [1,2,3]

Output: true

Example 2:

Input:     1         1
          /           \
         2             2

        [1,2],     [1,null,2]

Output: false

Example 3:

Input:     1         1
          / \       / \
         2   1     1   2

        [1,2,1],   [1,1,2]

Output: false

判断两棵二叉树是否相同。我第一想到的是对两棵树都先序遍历一下,然后看看遍历出来的序列是否一致,如果一致则树是一样的。

class Solution {
public:
    void preOrderTraverse(TreeNode* t, vector<int>& res)
    {
        if (t != NULL) {
            res.push_back(t->val);
            preOrderTraverse(t->left, res);
            preOrderTraverse(t->right, res);
        }
    }
    bool isSameTree(TreeNode* p, TreeNode* q)
    {
        vector<int> pRes, qRes;
        preOrderTraverse(p, pRes);
        preOrderTraverse(q, qRes);
        if (pRes.size() != qRes.size())
            return false;
        for (int i = 0; i < pRes.size(); i++) {
            if (pRes[i] != qRes[i])
                return false;
        }
        return true;
    }
};

但是这样做居然是错的!请注意,虽然两棵树的遍历结果是一样的,并不代表这两棵树的结构是一样的。比如[10,5,15]和[10,5,null,null,15],虽然先序遍历的结果都是10,5,15,但是这两棵树的结构是不一样的。所以不能只看遍历的结果,在遍历的时候就要判断是否相同。如果在同一个位置上,一颗节点为空,另一棵上有值,结构就不一样了。要么两个节点都为空或者有值且相等。
完整代码如下:

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q)
    {
        if (p == NULL && q == NULL)
            return true;
        if (p == NULL || q == NULL)
            return false;
        if (p->val != q->val)
            return false;
        return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }
};

本代码提交AC,用时0MS。

LeetCode Permutation Sequence

60. Permutation Sequence

The set [1,2,3,...,n] contains a total of n! unique permutations.

By listing and labeling all of the permutations in order, we get the following sequence for n = 3:

  1. "123"
  2. "132"
  3. "213"
  4. "231"
  5. "312"
  6. "321"

Given n and k, return the kth permutation sequence.

Note:

  • Given n will be between 1 and 9 inclusive.
  • Given k will be between 1 and n! inclusive.

Example 1:

Input: n = 3, k = 3
Output: "213"

Example 2:

Input: n = 4, k = 9
Output: "2314"

求1~n的所有排列中,第k大的数。肯定不可能先用LeetCode Permutations把所有排列找出来,然后sort取第k个,肯定TLE,所以换思路。

假设给的例子中,原数组为s=”123″,要求第5大的数,即”312″。我们首先找出这个数的首位数字是什么。我们知道以某个数字固定开头的排列个数=(n-1)!=2!=2,即以1和2开头的排列数共有2*2=4个,而我们要求的是第5大的数,所以这个数开头必须是3=5/2+1=ceil( k / (n-1)! )=m。
确定了第一位之后,把3从原数组s中删掉,然后递归查找剩余数组s=”12″中第5-2*2=1=k-(m-1)*(n-1)!位的排列。显然就是最小的“12”了,然后拼起来为”312″。

完整代码如下:

class Solution {
public:
    vector<int> fact(int n)
    {
        vector<int> vFact;
        vFact.push_back(1); // 0!=1
        int ans = 1;
        for (int i = 1; i <= n; i++) {
            ans *= i;
            vFact.push_back(ans);
        }
        return vFact;
    }
    char work(string& candidates, vector<int>& vFact, int& k)
    {
        int tmp = vFact[candidates.size() – 1];
        int idx = ceil(k / float(tmp));
        idx–;
        char ans = candidates[idx];
        candidates.erase(idx, 1);
        k -= idx * tmp;
        return ans;
    }
    string getPermutation(int n, int k)
    {
        vector<int> vFact = fact(n);
        string candidates = string("123456789").substr(0, n);
        string ans(n, ‘ ‘);
        for (int i = 0; i < n; i++)
            ans[i] = work(candidates, vFact, k);
        return ans;
    }
};

本代码提交AC,用时0MS。

二刷。直接DFS的过程就是从小到大排序的过程,DFS到第k个排列的时候返回即可:

class Solution {
public:
	void dfs(string &s, int n, vector<int>& visited, int k, int &m, string &ans) {
		if (m == k)return;

		if (s.size() == n) {
			++m;
			if (m == k)
				ans = s;
			return;
		}
		for (int i = 1; i <= n; ++i) {
			if (visited[i] == 0) {
				s.push_back('0' + i);
				visited[i] = 1;
				dfs(s, n, visited, k, m, ans);
				s.pop_back();
				visited[i] = 0;
			}
		}
	}
	string getPermutation(int n, int k) {
		string s = "", ans = "";
		vector<int> visited(n + 1, 0);
		int m = 0;
		dfs(s, n, visited, k, m, ans);
		return ans;
	}
};

本代码提交AC,用时1356MS,太慢了,还是用之前的方法吧。

LeetCode Pow(x, n)

50. Pow(x, n)

Implement pow(xn), which calculates x raised to the power n (xn).

Example 1:

Input: 2.00000, 10
Output: 1024.00000

Example 2:

Input: 2.10000, 3
Output: 9.26100

Example 3:

Input: 2.00000, -2
Output: 0.25000
Explanation: 2-2 = 1/22 = 1/4 = 0.25

Note:

  • -100.0 < x < 100.0
  • n is a 32-bit signed integer, within the range [−231, 231 − 1]

实现幂指数运算。 如果对x连乘n次,时间复杂度为$O(n)$,但使用分治法能降到$O(\log n)$。因为可以把$x^n$分解为: $\begin{equation*} x^n= \begin{cases} x^{\frac{n}{2}} \cdot x^{\frac{n}{2}}, & \text{if } n \text{ even}\\ x^{\frac{n-1}{2}} \cdot x^{\frac{n-1}{2}} \cdot x, & \text{if } n \text{ odd} \end{cases} \end{equation*}$

完整代码如下:

class Solution {
public:
    double doPow(double x, int n)
    {
        if (n == 0)
            return 1.0;
        double v = doPow(x, n / 2);
        if (n % 2 == 0)
            return v * v;
        else
            return v * v * x;
    }
    double myPow(double x, int n)
    {
        int m = n > 0 ? n : -n;
        double ans = doPow(x, m);
        return n > 0 ? ans : 1.0 / ans;
    }
};

本代码提交AC,用时3MS。
注意在子程序中不要调用两次doPow(x, n/2),算出一个v,然后直接v*v即可。

二刷。使用非递归的方法实现快速幂,代码如下:

class Solution {
public:
    double myPow(double x, int n)
    {
        if (n == 0)
            return 1;
        if (n == 1)
            return x;
        long long m = n; // 先转换为ll,否则n=INT_MIN是有问题
        m = m > 0 ? m : -m;
        double ans = 1;
        while (m) {
            if (m & 1)
                ans *= x;
            m >>= 1;
            x = x * x;
        }
        return n > 0 ? ans : 1 / ans;
    }
};

本代码提交AC,用时3MS。

三刷。注意n=INT_MIN时,第一种解法会有问题,需要用long long存储,代码如下:

class Solution {
public:
	double myPow2(double x, long long m) {
		if (x == 0.0)return 0.0;
		if (x == 1.0 || m == 0)return 1.0;
		if (m == 1)return x;
		if (m < 0) return 1.0 / myPow2(x, -m);

		if (m % 2 == 1)return x * myPow2(x*x, m >> 1);
		else return myPow2(x*x, m >> 1);
	}
	double myPow(double x, int n) {
		return myPow2(x, n);
	}
};

本代码提交AC,用时0MS。

LeetCode Maximum Subarray

53. Maximum Subarray

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

Follow up:

If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.


求最大连续子数组和,这是一道非常经典的题目,从本科时候就开始做了。有两种解法。

动态规划法

用dp数组存储到当前元素的最大子数组和,如dp[i-1]表示包含第i-1个元素的最大子数组和,则dp[i]表示包含第i个元素的最大子数组和。如果dp[i-1]>0,则dp[i]=dp[i-1]+nums[i];否则dp[i]=nums[i]。完整代码如下:

class Solution {
public:
    int maxSubArray(vector<int>& nums)
    {
        vector<int> dp(nums.size(), 0);
        dp[0] = nums[0];
        int ans = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            if (dp[i – 1] > 0)
                dp[i] = dp[i – 1] + nums[i];
            else
                dp[i] = nums[i];
            if (dp[i] > ans)
                ans = dp[i];
        }
        return ans;
    }
};

本代码只用扫描一遍数组,所以时间复杂度为$O(n)$。本代码提交AC,用时12MS。

分治法

将数组一分为二,比如数组[a,b]分为[a,m-1]和[m+1,b],递归求解[a,m-1]和[m+1,b]的最大连续子数组和lmax和rmax,但是[a,b]的最大连续子数组和还可能是跨过中点m的,所以还应该从m往前和往后求一个包含m的最大连续子数组和mmax,然后再求lmax,rmax,mmax的最大值。完整代码如下:

class Solution {
public:
    int divide(vector<int>& nums, int left, int right)
    {
        if (left == right)
            return nums[left];
        if (left == right – 1)
            return max(nums[left] + nums[right], max(nums[left], nums[right]));
        int mid = (left + right) / 2;
        int lmax = divide(nums, left, mid – 1);
        int rmax = divide(nums, mid + 1, right);
        int mmax = nums[mid], tmp = nums[mid];
        for (int i = mid – 1; i >= left; i–) {
            tmp += nums[i];
            if (tmp > mmax)
                mmax = tmp;
        }
        tmp = mmax;
        for (int i = mid + 1; i <= right; i++) {
            tmp += nums[i];
            if (tmp > mmax)
                mmax = tmp;
        }
        return max(mmax, max(lmax, rmax));
    }
    int maxSubArray(vector<int>& nums) { return divide(nums, 0, nums.size() – 1); }
};

本代码可以类比平面上求最近点对的例子,需要考虑跨界的问题,时间复杂度为$O(nlgn)$。本代码提交AC,用时也是12MS。

综合来看,还是动态规划法更漂亮。

二刷。动态规划还可以再简化,即不用额外的DP数组,代码如下:

class Solution {
public:
	int maxSubArray(vector<int>& nums) {
		int n = nums.size();
		int max_sum = INT_MIN, cur_sum = 0;
		for (int i = 0; i < n; ++i) {
			if (cur_sum >= 0) {
				cur_sum += nums[i];
			}
			else {
				cur_sum = nums[i];
			}
			max_sum = max(max_sum, cur_sum);
		}
		return max_sum;
	}
};

本代码提交AC,用时4MS,击败98%用户。

LeetCode Combination Sum II

40. Combination Sum II

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

Each number in candidates may only be used once in the combination.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

Input: candidates = [10,1,2,7,6,1,5], target = 8,
A solution set is:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

Example 2:

Input: candidates = [2,5,2,1,2], target = 5,
A solution set is:
[
  [1,2,2],
  [5]
]

这题和之前的LeetCode Combination Sum很类似,但是要求同一个数字不能重复选,不过candidates里面可能会有重复的数字。所以策略就是在一个for循环里面判断数字是否重复,注意不能在递归里面判断所选数字是否重复,因为递归的时候是可以选相同数字的,比如样例的[1,1,6]。同时递归的时候传入当前下标的下一个下标。
完整代码如下:

class Solution {
public:
    void work(vector<vector<int> >& ans, vector<int>& candidates, int idx, vector<int>& sol, int target)
    {
        if (target == 0) {
            ans.push_back(sol);
        }
        else {
            for (int i = idx; i < candidates.size(); i++) {
                if (candidates[i] > target)
                    break;
                if (i != idx && candidates[i] == candidates[i – 1])
                    continue; // i != idx
                sol.push_back(candidates[i]);
                work(ans, candidates, i + 1, sol, target – candidates[i]); // i + 1
                sol.pop_back();
            }
        }
    }
    vector<vector<int> > combinationSum2(vector<int>& candidates, int target)
    {
        vector<vector<int> > ans;
        vector<int> sol;
        sort(candidates.begin(), candidates.end());
        work(ans, candidates, 0, sol, target);
        return ans;
    }
};

本代码提交AC,用时10MS。

二刷。相比于LeetCode Combination Sum,这一题确实需要对输入数组排序,与上述解法类似,代码如下:

class Solution {
public:
	void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& cur, int idx) {
		if (target == 0) {
			ans.push_back(cur);
			return;
		}
		if (target < 0)return;
		int i = idx;
		while (i < candidates.size()) {
			if (i > idx&&candidates[i] == candidates[i - 1]) {
				++i;
				continue;
			}
			target -= candidates[i];
			cur.push_back(candidates[i]);
			dfs(candidates, target, ans, cur, i + 1);
			target += candidates[i];
			cur.pop_back();
			++i;
		}
	}
	vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
		vector<vector<int>> ans;
		vector<int> cur;
		sort(candidates.begin(), candidates.end());
		dfs(candidates, target, ans, cur, 0);
		return  ans;
	}
};

本代码提交AC,用时8MS。

LeetCode Combination Sum

39. Combination Sum

Given a set of candidate numbers (candidates(without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

The same repeated number may be chosen from candidates unlimited number of times.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

Input: candidates = [2,3,6,7], target = 7,
A solution set is:
[
  [7],
  [2,2,3]
]

Example 2:

Input: candidates = [2,3,5], target = 8,
A solution set is:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

本题要从一个集合中取出一些数,使得和为某个特定的数target,输出所有的这种组合。注意不能有重复的组合,比如[2,2,3]和[3,2,2]算是重复的。

这是很常见的组合问题,使用递归的思路求解。比如样例,要求和为7,则每个加数必须小于等于7,所以依次尝试2,3,6,7,如果是2,则还差7-2=5,所以递归的在[2,3,6,7]里面找和为5的组合。如此递归,直到差为0。但是这样有一个问题,比如你这一次尝试的是2,则可能得到[2,2,3]的组合,下一次尝试3,则可能得到[3,2,2]的组合,但是这两个组合是相同的。我一开始的想法是对组合排序,然后用set去重。代码如下:

class Solution {
public:
    void work(set<vector<int> >& ans, vector<int>& candidates, vector<int>& sol, int target)
    {
        if (target == 0) {
            vector<int> tmp = sol;
            sort(tmp.begin(), tmp.end());
            ans.insert(tmp);
        }
        else {
            for (int i = 0; i < candidates.size(); i++) {
                if (candidates[i] > target)
                    break;
                sol.push_back(candidates[i]);
                work(ans, candidates, sol, target – candidates[i]);
                sol.pop_back();
            }
        }
    }
    vector<vector<int> > combinationSum(vector<int>& candidates, int target)
    {
        set<vector<int> > ans;
        vector<int> sol;
        sort(candidates.begin(), candidates.end());
        work(ans, candidates, sol, target);
        return vector<vector<int> >(ans.begin(), ans.end());
    }
};


本代码提交AC,但是用时145MS,只击败了4.5%的人,所以肯定还有更优的方案。

上述方案的一个费时的地方就是要对每个组合排序,然后用set去重,如果能省掉这个环节,速度会快很多。

网上查看题解之后,恍然大悟,每次只选不小于上一次选过的数,这样得到的组合就不会有重复,不需要排序,也不需要set去重。比如上面的例子,在选到3时,差7-3=4,此时再从[2,3,6,7]里面选时,只选大于等于3的数,也就是3,6,7,不再考虑2了。但是6,7都大于4,不行;3的话,剩余4-3=1也无解。所以搜3的时候,就不会得到[3,2,2]的结果,也就无需去重。
完整代码如下:

class Solution {
public:
    void work(vector<vector<int> >& ans, vector<int>& candidates, vector<int>& sol, int target)
    {
        if (target == 0) {
            ans.push_back(sol);
        }
        else {
            for (int i = 0; i < candidates.size(); i++) {
                if (candidates[i] > target || (sol.size() > 0 && candidates[i] < sol[sol.size() – 1]))
                    continue;
                sol.push_back(candidates[i]);
                work(ans, candidates, sol, target – candidates[i]);
                sol.pop_back();
            }
        }
    }
    vector<vector<int> > combinationSum(vector<int>& candidates, int target)
    {
        vector<vector<int> > ans;
        vector<int> sol;
        sort(candidates.begin(), candidates.end());
        work(ans, candidates, sol, target);
        return ans;
    }
};

本代码提交AC,用时20MS,速度快了好多。

后来我发现这个版本的代码还可以优化,因为candidates已经排好序了,如果这次取了i号元素,下一次只要取i及以后的元素即可,所以在递归的时候多传入一个下标值,也能提速。完整代码如下:

class Solution {
public:
    void work(vector<vector<int> >& ans, vector<int>& candidates, int idx, vector<int>& sol, int target)
    {
        if (target == 0) {
            ans.push_back(sol);
        }
        else {
            for (int i = idx; i < candidates.size(); i++) {
                if (candidates[i] > target)
                    break;
                sol.push_back(candidates[i]);
                work(ans, candidates, i, sol, target – candidates[i]);
                sol.pop_back();
            }
        }
    }
    vector<vector<int> > combinationSum(vector<int>& candidates, int target)
    {
        vector<vector<int> > ans;
        vector<int> sol;
        sort(candidates.begin(), candidates.end());
        work(ans, candidates, 0, sol, target);
        return ans;
    }
};

本代码提交AC,用时16MS。

二刷。没必要排序,直接DFS选当前及后续元素即可,代码如下:

class Solution {
public:
	void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& cur, int idx) {
		if (target == 0) {
			ans.push_back(cur);
			return;
		}
		if (target < 0)return;
		for (int i = idx; i < candidates.size(); ++i) {
			target -= candidates[i];
			cur.push_back(candidates[i]);
			dfs(candidates, target, ans, cur, i);
			cur.pop_back();
			target += candidates[i];
		}
	}
	vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
		vector<vector<int>> ans;
		vector<int> cur;
		dfs(candidates, target, ans, cur, 0);
		return ans;
	}
};

本代码提交AC,用时12MS。

LeetCode Permutations II

47. Permutations II

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

Example:

Input: [1,1,2]
Output:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

这道题和之前的Permutations很像,只不过此时包含有重复元素。 在Permutations里,我使用了一个一个往里加的思路,今天我使用另一个思路。
为了得到一个序列的所有排列,可以采用每个元素和当前第一个元素交换的思路。比如要得到1,2,3的所有排列,第一轮每个元素和第一个元素交换得到

  • 1,2,3
  • 2,1,3
  • 3,2,1

此时每个元素都有打头的序列出现,再对每一个序列,除掉第一个元素,循环交换。比如对于1,2,3,此时把2,3作为一个新的序列,和此时的第一个元素2交换,得到序列:

  • 2,3
  • 3,2

然后再和之前的元素1拼接起来,得到以1开头的所有排列:

  • 1,2,3
  • 1,3,2

同理能得到以2,3打头的所有排列。用这种思路也可以求解Permutations这个题。当遇到有重复元素时,可以先对数组排序,在交换的时候,如果当前元素和前一个元素相同,则不再交换。最后再用set去重,完整代码如下:

class Solution {
public:
    void work(set<vector<int> >& ans, vector<int>& nums, int start)
    {
        if (start == nums.size()) {
            ans.insert(nums);
        }
        else {
            for (int i = start; i < nums.size(); i++) {
                if (i != start && nums[i] == nums[i – 1])
                    continue;
                swap(nums[i], nums[start]);
                work(ans, nums, start + 1);
                swap(nums[i], nums[start]);
            }
        }
    }
    vector<vector<int> > permuteUnique(vector<int>& nums)
    {
        sort(nums.begin(), nums.end());
        set<vector<int> > ans;
        work(ans, nums, 0);
        return vector<vector<int> >(ans.begin(), ans.end());
    }
};

本代码提交AC,用时48ms。速度比较慢,应该还有其他更优的算法。


新方法,使用和Permutations类似的dfs递归方法,只不过在递归的时候,判断一下,保证这次递归的元素不能和上一次相同。比如数组是[1,1,2],第一次从第一个1开始递归,那么在这层循环中,下一次递归就不能从第二个1开始了,但是下一层循环中还是应该可以选第二个1的。理解起来有点绕,假设我们把求[1,1,2]的全排列想象成从[1,1,2]中无放回的取数字,依先后顺序扔到3个格子中,每个格子只能放一个数字。那么第一次拿的数可能是1,1,2,则第一次拿第一个1和第二个1产生的全排列应该是完全一样的。

代码如下。第11行:用last记录本次循环中上一个递归的数,那么本次循环不再对同一个数递归了。因为先对数组排序了,所以所有相同的数字都会聚到一起,而且这样产生的全排列是按大小顺序排列的。

class Solution {
private:
    void dfs(const vector<int>& nums, vector<vector<int> >& ans, vector<int>& cand, vector<int>& visited)
    {
        if (cand.size() == nums.size()) {
            ans.push_back(cand);
            return;
        }
        int last = INT_MAX;
        for (int i = 0; i < nums.size(); ++i) {
            if (visited[i] == 0) {
                if (i != 0 && nums[i] == last)
                    continue; // 不能和上一次递归的元素相同,否则产生冗余排列
                cand.push_back(nums[i]);
                last = nums[i];
                visited[i] = 1;
                dfs(nums, ans, cand, visited);
                visited[i] = 0;
                cand.pop_back();
            }
        }
    }
public:
    vector<vector<int> > permuteUnique(vector<int>& nums)
    {
        sort(nums.begin(), nums.end());
        vector<vector<int> > ans;
        vector<int> cand, visited(nums.size(), 0);
        dfs(nums, ans, cand, visited);
        return ans;
    }
};

本代码提交AC,用时23MS,比前一种方法快,省掉了set去重。

二刷。上述解法固然正确,但是用INT_MAX来初始化last还是有风险的,万一数组中有INT_MAX呢。
更好的方法其实是,不用last记录上一次递归的值,二刷直接在内层if判断,如果i!=0且nums[i]==nums[i-1],说明这个数和上一个数相同,但是此时还不能continue,万一有[1,1,2]这种情况,在外层递归第二个1时,第一个1在上一轮是已经恢复了visited[0]=0的,所以这里可以加上一个visited[i-1]==0。完整代码如下:

class Solution {
private:
    void dfs(const vector<int>& nums, vector<vector<int> >& ans, vector<int>& cand, vector<int>& visited)
    {
        if (cand.size() == nums.size()) {
            ans.push_back(cand);
            return;
        }
        for (int i = 0; i < nums.size(); ++i) {
            if (visited[i] == 0) {
                if (i != 0 && nums[i] == nums[i – 1] && visited[i – 1] == 0)
                    continue;
                cand.push_back(nums[i]);
                visited[i] = 1;
                dfs(nums, ans, cand, visited);
                visited[i] = 0;
                cand.pop_back();
            }
        }
    }
public:
    vector<vector<int> > permuteUnique(vector<int>& nums)
    {
        sort(nums.begin(), nums.end());
        vector<vector<int> > ans;
        vector<int> cand, visited(nums.size(), 0);
        dfs(nums, ans, cand, visited);
        return ans;
    }
};

本代码提交AC,用时23MS。

三刷。判断条件还可以再简单些,用pre记录上次递归的下标即可。

class Solution {
public:
	void dfs(vector<int>& nums, vector<int>& visited, vector<vector<int>>& ans, vector<int>& cur) {
		if (cur.size() == nums.size()) {
			ans.push_back(cur);
			return;
		}
		int pre = -1;
		for (int i = 0; i < nums.size(); ++i) {
			if (visited[i] == 0) {
				if (pre != -1 && nums[i] == nums[pre])continue;
				visited[i] = 1;
				cur.push_back(nums[i]);
				dfs(nums, visited, ans, cur);
				cur.pop_back();
				visited[i] = 0;
				pre = i;
			}
		}
	}
	vector<vector<int>> permuteUnique(vector<int>& nums) {
		sort(nums.begin(), nums.end());
		vector<vector<int>> ans;
		vector<int> visited(nums.size(), 0);
		vector<int> cur;
		dfs(nums, visited, ans, cur);
		return ans;
	}
};

本代码提交AC,用时24MS。

LeetCode Permutations

46. Permutations

Given a collection of distinct integers, return all possible permutations.

Example:

Input: [1,2,3]
Output:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

这道题要产生一个数组的所有排列情况,可以使用递归的思想,一个一个元素往里加,直到达到个数要求。使用flag来标记是否添加过。和我最近在做的蛋白质搜索引擎中加修饰的情况很类似,实现如下:

class Solution {
public:
    void work(vector<vector<int> >& ans, vector<int>& tmp, vector<int>& nums, vector<int>& flag)
    {
        if (tmp.size() == nums.size()) {
            ans.push_back(tmp);
        }
        else {
            for (int i = 0; i < flag.size(); i++) {
                if (flag[i] == 1) {
                    vector<int> next = tmp;
                    next.push_back(nums[i]);
                    flag[i] = 0;
                    work(ans, next, nums, flag);
                    flag[i] = 1;
                }
            }
        }
    }
    vector<vector<int> > permute(vector<int>& nums)
    {
        vector<int> flag(nums.size(), 1);
        vector<vector<int> > ans;
        vector<int> tmp;
        work(ans, tmp, nums, flag);
        return ans;
    }
};

本代码提交AC,用时21MS。

二刷。其实上述代码中不需要复制一个next数组的,代码如下:

class Solution {
private:
    void DFS(const vector<int>& nums, vector<int>& visited, vector<int>& candidate, vector<vector<int> >& ans)
    {
        if (candidate.size() == nums.size()) {
            ans.push_back(candidate);
            return;
        }
        for (int i = 0; i < nums.size(); ++i) {
            if (visited[i] == 0) {
                visited[i] = 1;
                candidate.push_back(nums[i]);
                DFS(nums, visited, candidate, ans);
                visited[i] = 0;
                candidate.pop_back();
            }
        }
    }
public:
    vector<vector<int> > permute(vector<int>& nums)
    {
        vector<int> visited(nums.size(), 0), candidate;
        vector<vector<int> > ans;
        DFS(nums, visited, candidate, ans);
        return ans;
    }
};

本代码提交AC,用时9MS,击败79%。

还可以不需要visited和candidate数组,用交换的思路,代码如下:

class Solution {
private:
    void DFS(vector<int>& nums, vector<vector<int> >& ans, int start)
    {
        if (start == nums.size()) {
            ans.push_back(nums);
            return;
        }
        for (int i = start; i < nums.size(); ++i) {
            swap(nums[i], nums[start]);
            DFS(nums, ans, start + 1);
            swap(nums[i], nums[start]);
        }
    }
public:
    vector<vector<int> > permute(vector<int>& nums)
    {
        vector<vector<int> > ans;
        DFS(nums, ans, 0);
        return ans;
    }
};

本代码提交AC,用时16MS。

LeetCode Count and Say

38. Count and Say

The count-and-say sequence is the sequence of integers with the first five terms as following:

1.     1
2.     11
3.     21
4.     1211
5.     111221

1 is read off as "one 1" or 11.
11 is read off as "two 1s" or 21.
21 is read off as "one 2, then one 1" or 1211.

Given an integer n where 1 ≤ n ≤ 30, generate the nth term of the count-and-say sequence. You can do so recursively, in other words from the previous member read off the digits, counting the number of digits in groups of the same digit.

Note: Each term of the sequence of integers will be represented as a string.

Example 1:

Input: 1
Output: "1"
Explanation: This is the base case.

Example 2:

Input: 4
Output: "1211"
Explanation: For n = 3 the term was "21" in which we have two groups "2" and "1", "2" can be read as "12" which means frequency = 1 and value = 2, the same way "1" is read as "11", so the answer is the concatenation of "12" and "11" which is "1211".

这题目英文描述真是醉了,看半天没理解意思,还是看网上翻译的。其实就是后一个字符串是前一个字符串的read,比如第三个字符串是21,它包含1个2和1个1,所以第四个字符串就是1211。 果然是easy模式,用递归几行代码搞定。

class Solution {
public:
    string countAndSay(int n)
    {
        if (n == 1)
            return "1";
        string pre = countAndSay(n – 1);
        string ans = "";
        int i = 0, j;
        while (i < pre.size()) {
            j = i + 1;
            while (j < pre.size() && pre[j] == pre[i])
                j++;
            ans += to_string(j – i) + pre.substr(i, 1);
            i = j;
        }
        return ans;
    }
};

本代码提交AC,用时4MS。