Tag Archives: 递归

LeetCode Binary Tree Level Order Traversal

102. Binary Tree Level Order Traversal

Given a binary tree, return the level order traversal of its nodes’ values. (ie, from left to right, level by level).

For example:
Given binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

return its level order traversal as:

[
  [3],
  [9,20],
  [15,7]
]

本题要求实现二叉树的层次遍历,居然没要求同时实现递归和迭代版本了,不过要求分层输出,还真是头一回见。
层次遍历不难,用队列+BFS实现,但是为了实现分层输出,我的方法是标记下一层的第一个节点,当队列走到下一层第一个节点时,说明该层结束,进入下一个循环。该版本的完整代码如下:

class Solution {
    public: void bfs(vector < vector < int >> & ans, TreeNode * root) {
        queue < TreeNode * > tree;
        tree.push(root);
        TreeNode * front;
        while (!tree.empty()) {
            vector < int > cand;
            bool pushed = false;
            TreeNode * next_level = NULL; // 下一层的第一个节点 
            front = tree.front();
            while (!tree.empty() && front != next_level) {
                tree.pop();
                cand.push_back(front - > val);
                if (front - > left != NULL) {
                    tree.push(front - > left);
                    if (!pushed) {
                        next_level = front - > left; // 标记 
                        pushed = true; // 标记 
                    }
                }
                if (front - > right != NULL) {
                    tree.push(front - > right);
                    if (!pushed) {
                        next_level = front - > right; // 标记 
                        pushed = true; // 标记 

                    }
                }
                if (tree.empty()) break;
                front = tree.front();
            }
            ans.push_back(cand);
        }
    }
    vector < vector < int >> levelOrder(TreeNode * root) {
        vector < vector < int >> ans;
        if (root == NULL) return ans;
        bfs(ans, root);
        return ans;
    }
};

本代码提交AC,用时9MS。
但是这个版本的代码看起来不够简洁,网上看题解发现递归迭代各一种简洁漂亮的解法。
我的解法不够简洁的原因是需要记录下一层第一个节点,然后判断当前层是否已遍历结束。但是仔细想想,这一层需要遍历的次数不就是这一层的节点数吗,而节点都存在队列中了,所以可以获取队列大小n,每层只循环n次即可。这个版本的代码如下,简洁了许多。

class Solution {
    public: vector < vector < int >> levelOrder(TreeNode * root) {
        vector < vector < int >> ans;
        if (root == NULL) return ans;
        queue < TreeNode * > tree;
        tree.push(root);
        TreeNode * f;
        while (!tree.empty()) {
            int n = tree.size();
            vector < int > one_level;
            for (int i = 0; i < n; i++) {
                f = tree.front();
                tree.pop();
                one_level.push_back(f - > val);
                if (f - > left != NULL) tree.push(f - > left);
                if (f - > right != NULL) tree.push(f - > right);
            }
            ans.push_back(one_level);
        }
        return ans;
    }
};

本代码提交AC,用时6MS,也稍快了一点。
至于递归版本,我是没想到层次遍历也可以用递归来实现。递归的思路是,当当前遍历的层数和结果二维数组中的大小相等时,说明此时是第一次走到这一层,需要往二维数组中加一个空的一维数组,同时把这个节点的值加进去。然后依次先左后右的递归下去。
注意用递归实现层次遍历时,并不和我们肉眼层次遍历的顺序一致,它相当于DFS,只是把深搜的结果存到指定层的数组中了,最终输出的效果和普通的层次遍历一样。递归版本的代码如下:

 class Solution {
     public: void work(vector < vector < int >> & ans, int level, TreeNode * root) {
         if (ans.size() == level) ans.push_back({}); // 重点
         ans[level].push_back(root - > val);
         if (root - > left != NULL) work(ans, level + 1, root - > left); // 先左
         if (root - > right != NULL) work(ans, level + 1, root - > right); // 后右
     }
     vector < vector < int >> levelOrder(TreeNode * root) {
         vector < vector < int >> ans;
         if (root == NULL) return ans;
         work(ans, 0, root);
         return ans;
     }
 };

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

LeetCode Path Sum II

113. Path Sum II

Given a binary tree and a sum, find all root-to-leaf paths where each path’s sum equals the given sum.

Note: A leaf is a node with no children.

Example:

Given the below binary tree and sum = 22,

      5
     / \
    4   8
   /   / \
  11  13  4
 /  \    / \
7    2  5   1

Return:

[
   [5,4,11,2],
   [5,8,4,5]
]

相比上一题LeetCode Path Sum更进一步,本题要把所有等于sum的路径记录下来,所以递归求解的时候要带着一个数组,把从根节点到当前节点的路径记录下来。常规题,完整代码如下:

class Solution {
public:
    void dfs(vector<vector<int> >& ans, vector<int>& path, int& target, int cur, TreeNode* root)
    {
        if (root->left == NULL && root->right == NULL) {
            if (cur + root->val == target) {
                path.push_back(root->val);
                ans.push_back(path);
                path.pop_back();
            }
            return;
        }
        if (root->left != NULL) {
            path.push_back(root->val);
            dfs(ans, path, target, cur + root->val, root->left);
            path.pop_back();
        }
        if (root->right != NULL) {
            path.push_back(root->val);
            dfs(ans, path, target, cur + root->val, root->right);
            path.pop_back();
        }
    }
    vector<vector<int> > pathSum(TreeNode* root, int sum)
    {
        vector<vector<int> > ans;
        if (root == NULL)
            return ans;
        vector<int> path;
        dfs(ans, path, sum, 0, root);
        return ans;
    }
};

本代码提交AC,用时16MS。
二刷。更简洁的代码,如下:

class Solution {
private:
    void dfs(vector<vector<int> >& ans, vector<int>& candidate, TreeNode* root, int sum)
    {
        candidate.push_back(root->val);
        sum -= root->val;
        if (root->left == NULL && root->right == NULL && sum == 0) {
            ans.push_back(candidate);
        }
        if (root->left != NULL)
            dfs(ans, candidate, root->left, sum);
        if (root->right != NULL)
            dfs(ans, candidate, root->right, sum);
        candidate.pop_back();
    }
public:
    vector<vector<int> > pathSum(TreeNode* root, int sum)
    {
        if (root == NULL)
            return {};
        vector<vector<int> > ans;
        vector<int> candidate;
        dfs(ans, candidate, root, sum);
        return ans;
    }
};

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

三刷。更易读代码:

class Solution {
public:
	void dfs(TreeNode *root, vector<vector<int>> &ans, vector<int> &cand, int sum) {
		if (root->left == NULL && root->right == NULL && sum == 0) {
			ans.push_back(cand);
			return;
		}
		if (root->left != NULL) {
			cand.push_back(root->left->val);
			sum -= root->left->val;
			dfs(root->left, ans, cand, sum);
			sum += root->left->val;
			cand.pop_back();
		}
		if (root->right != NULL) {
			cand.push_back(root->right->val);
			sum -= root->right->val;
			dfs(root->right, ans, cand, sum);
			sum += root->right->val;
			cand.pop_back();
		}
	}
	vector<vector<int>> pathSum(TreeNode* root, int sum) {
		if (root == NULL)return {};
		vector<vector<int>> ans;
		vector<int> cand = { root->val };
		dfs(root, ans, cand, sum - root->val);
		return ans;
	}
};

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

LeetCode Path Sum

112. Path Sum

Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.

Note: A leaf is a node with no children.

Example:

Given the below binary tree and sum = 22,

      5
     / \
    4   8
   /   / \
  11  13  4
 /  \      \
7    2      1

return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.


本题要一个数是否等于从树根到某个叶子的和。简单题,直接dfs把所有从根到叶子的路径和算出来,存到set里面,然后用sum去set里查。当然如果还想更高效,可以用hash,比如unordered_map。 完整代码如下:

class Solution {
public:
    void dfs(set<int>& vals, int cur, TreeNode* root)
    {
        if (root->left == NULL && root->right == NULL) {
            vals.insert(cur + root->val);
            return;
        }
        if (root->left != NULL) {
            dfs(vals, cur + root->val, root->left);
        }
        if (root->right != NULL) {
            dfs(vals, cur + root->val, root->right);
        }
    }
    bool hasPathSum(TreeNode* root, int sum)
    {
        if (root == NULL)
            return false;
        set<int> vals;
        dfs(vals, 0, root);
        if (vals.count(sum) > 0)
            return true;
        else
            return false;
    }
};

本代码提交AC,用时13MS。
二刷。直接递归判断某一条路径的和是否等于sum就好了,代码如下:

class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum)
    {
        if (root == NULL)
            return false;
        if (root->left == NULL && root->right == NULL && root->val == sum)
            return true;
        if (root->left == NULL && root->right == NULL && root->val != sum)
            return false;
        if (root->left != NULL)
            if (hasPathSum(root->left, sum – root->val))
                return true;
        if (root->right != NULL)
            if (hasPathSum(root->right, sum – root->val))
                return true;
        return false;
    }
};

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

LeetCode Binary Tree Postorder Traversal

145. Binary Tree Postorder Traversal

Given a binary tree, return the postorder traversal of its nodes’ values.

Example:

Input: [1,null,2,3]
   1
    \
     2
    /
   3

Output: [3,2,1]

Follow up: Recursive solution is trivial, could you do it iteratively?


实现二叉树的后续遍历算法,需要非递归形式哦~这题好难…是三种非递归遍历算法中最难的一种了。
惯例,先上递归算法:

class Solution {
public:
    void work(vector<int>& ans, TreeNode* root)
    {
        if (root == NULL)
            return;
        work(ans, root->left);
        work(ans, root->right);
        ans.push_back(root->val);
    }
    vector<int> postorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        work(ans, root);
        return ans;
    }
};

本代码提交AC,用时3MS。
下面来想非递归算法。续遍历的顺序是:左右,根节点是最后访问的。还是用堆栈来实现,那么根据堆栈的后进先出性质,压栈的顺序应该是根右左
那么什么时候可以访问根节点呢,只有当这个节点是叶子节点或者左右孩子都访问完之后才可以。判断是否为叶子节点很容易,那么怎么判断左右孩子都访问完了呢,此时我们可以用一个变量pre来记录上一次访问的节点,curr为当前栈顶节点。如果pre是curr的左或右孩子节点,则说明左右孩子已经访问过了,此时我们就可以访问curr了。
本算法的代码如下:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        if (root == NULL)
            return ans;
        stack<TreeNode*> tree;
        TreeNode *curr, *pre = NULL;
        tree.push(root); // 根
        while (!tree.empty()) {
            curr = tree.top();
            if ((curr->left == NULL && curr->right == NULL) || // 已到叶子节点
                (pre != NULL && (curr->left == pre || curr->right == pre))) { // 回溯的时候
                ans.push_back(curr->val);
                tree.pop();
                pre = curr;
            }
            else {
                if (curr->right != NULL)
                    tree.push(curr->right); // 右
                if (curr->left != NULL)
                    tree.push(curr->left); // 左
            }
        }
        return ans;
    }
};

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

可能有同学担心curr->left==pre不能一定说明左右孩子都访问完了,只能说明左孩子访问完了。但是根据我们的压栈顺序,当curr同时又左右孩子的时候,不会走这条判断。如下左图所示,首先4会弹栈,然后栈顶变为了5,此时pre=4, curr=5,访问5;然后5弹栈,栈顶为2,此时pre=5,curr=2,所以走的是curr->right==pre,访问2。只有当curr只有左孩子没有右孩子的时候才会判断curr->left==pre,如下右图,此时是可以访问curr的。

还有一种很简洁漂亮的解法,用两个堆栈。再次回忆序遍历顺序:左右,我们怎样用两个堆栈得到这样的顺序呢。如下图所示,第一个堆栈压栈顺序是左右根,然后弹出来压到第二个堆栈里,则从第二个堆栈里弹出来的顺序就是左右根了。注意,虽然第一次压栈的顺序已经是左右根了,我们要得到的也是左右根,为什么还要经过第二个堆栈这样倒腾一下呢?因为堆栈1的左右根和从堆栈2出来的左右根是不一样的,前者是靠近根节点的左右根,但是我们的后序遍历是要从叶子节点开始得到左右根,而从堆栈2出来的就是从叶子节点开始的左右根。

下面结合代码来说:

 class Solution {
   public: vector < int > postorderTraversal(TreeNode * root) {
     vector < int > ans;
     if (root == NULL) return ans;
     stack < TreeNode * > s1, s2;
     TreeNode * top;
     s1.push(root); // 根 
     while (!s1.empty()) {
       top = s1.top(); // (1)
       s1.pop(); // (1)
       s2.push(top); // (1)
       if (top - > left != NULL) {
         // 左
         s1.push(top - > left);
       }
       if (top - > right != NULL) {
         // 右 
         s1.push(top - > right);
       }
     }
     while (!s2.empty()) {
       top = s2.top();
       ans.push_back(top - > val);
       s2.pop();
     }
     return ans;
   }
 };

从第13、16行可知,对于堆栈1,我们先压了左后压了右,但是从第8行来看,根是最先压的,看起来不符合左右根的压栈顺序,但是请注意,代码(1)部分在先于13、16行弹栈了,然后马上压到堆栈2了,这样的效果不就是根节点在堆栈1的栈顶,然后被压到了堆栈2的底部了吗。所以和上图是统一的。

本代码提交AC,用时0MS。
定睛一看,这居然是道hard题。。。

三刷。这一题和中序遍历的迭代解法很类似。后序遍历是左右根,依然是要最先访问左子树的左叶子,所以依然可以使用函数putAllLeft将根沿着左子树的节点都放到栈中,只是在弹栈访问时,需要注意,如果该节点的右孩子已经访问,则无需再调用putAllLeft了,否则会导致死循环。所以在访问某个节点时,记录一下,存到pre中,作为下一个节点访问时上一个访问的节点。如果当前要访问的右孩子为pre,则说明右孩子已经访问过了,无需对右孩子调用putAllLeft。

完整代码如下:

class Solution {
public:
	void PutAllLeft(TreeNode *root, stack<TreeNode*> &stk) {
		while (root != NULL) {
			stk.push(root);
			root = root->left;
		}
	}
	vector<int> postorderTraversal(TreeNode* root) {
		vector<int> ans;
		stack<TreeNode*> stk;
		PutAllLeft(root, stk);
		TreeNode *pre = NULL;
		while (!stk.empty()) {
			TreeNode *cur = stk.top();
			if (cur->right != NULL && cur->right != pre) {
				PutAllLeft(cur->right, stk);
			}
			else {
				ans.push_back(cur->val);
				stk.pop();
				pre = cur;
			}
		}
		return ans;
	}
};

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

LeetCode Binary Tree Preorder Traversal

144. Binary Tree Preorder Traversal

Given a binary tree, return the preorder traversal of its nodes’ values.

Example:

Input: [1,null,2,3]
   1
    \
     2
    /
   3

Output: [1,2,3]

Follow up: Recursive solution is trivial, could you do it iteratively?


这一题要实现二叉树的前序遍历,序遍历的顺序是:左右。
首先献上妇孺皆知的递归解法:

class Solution {
public:
    void work(vector<int>& ans, TreeNode* root)
    {
        if (root == NULL)
            return;
        ans.push_back(root->val);
        work(ans, root->left);
        work(ans, root->right);
    }
    vector<int> preorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        work(ans, root);
        return ans;
    }
};

本代码提交AC,用时3MS。和LeetCode Binary Tree Inorder Traversal一样,本题也要实现前序遍历的迭代算法。迭代的思路同样是用堆栈来实现:首先把当前根节点压栈,然后注意,因为前序遍历是先访问左子树,后访问右子树,所以我们压栈的时候,要先压右子树,再压左子树,这样弹栈的时候才能符合前序遍历的要求。除此之外,我觉得实现起来比中序遍历要简单。
迭代版本的完整代码如下:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        if (root == NULL)
            return ans;
        stack<TreeNode*> tree;
        tree.push(root);
        TreeNode* top;
        while (!tree.empty()) {
            top = tree.top();
            ans.push_back(top->val);
            tree.pop();
            if (top->right != NULL) { // 先压栈右子树
                tree.push(top->right);
            }
            if (top->left != NULL) { // 再压栈左子树
                tree.push(top->left);
            }
        }
        return ans;
    }
};

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

LeetCode Binary Tree Inorder Traversal

94. Binary Tree Inorder Traversal

Given a binary tree, return the inorder traversal of its nodes’ values.

Example:

Input: [1,null,2,3]
   1
    \
     2
    /
   3

Output: [1,3,2]

Follow up: Recursive solution is trivial, could you do it iteratively?


本题要实现二叉树的中序遍历。这个题应该是很基本的二叉树的遍历题,本科学数据结构对这个是基本要求。不过本科学的是中文教材,只知道前序、中序、后序,不知道对应的英文是什么,WIKI上的Tree traversal图解还是很通俗易懂的:前序(Pre-order),中序(In-order),后序(Post-order)和层次遍历(Level-order)。
前序、中序和后序的区别很容易记,只要记住他们的前、中、后分别是访问根节点的顺序。比如前序顺序是先访问根节点,然后依次访问左右孩子节点。序:左右;序:左右;序:左右
本题要实现二叉树的中序遍历,如上所述,先访问左子树,然后访问根节点,最后访问右子树。用递归的方法非常简洁直观。递归的完整代码如下:

class Solution {
public:
    void work(vector<int>& ans, TreeNode* root)
    {
        if (root == NULL)
            return;
        work(ans, root->left); // 左
        ans.push_back(root->val); // 根
        work(ans, root->right); // 右
    }
    vector<int> inorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        work(ans, root);
        return ans;
    }
};

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

但是Note问能不能用迭代实现以下呢?当然可以,所以可以用递归实现的算法肯定都可以用迭代的方法实现,只是有时候迭代写起来更麻烦。
我的思路是肯定要首先找到树的最左下角的叶子节点,这个节点是最先被访问的,然后访问根节点,最后访问右子树。访问右子树的时候,又是重复之前的操作,首先找到右子树中的最左下角的叶子节点,访问它,然后访问根节点,最后又访问其右子树。
上述过程需要在访问完左孩子之后再访问根节点,但是树中节点的指向是单向的,只能由根访问到孩子,不能由孩子回溯到根。我们不可能在找孩子的同时保存该孩子的父亲节点,这样太麻烦了。于是我想到了用堆栈,在找最左下角的叶子节点的时候,顺便把经过的祖先节点压到堆栈中,当访问完孩子节点后,弹栈,此时的栈顶节点就是父亲节点了。如果父节点有右子树,则进入右子树,重复之前的过程(找右子树的最左下角叶子)。
迭代法的完整代码如下:

class Solution {
public:
    void putAllLeft(TreeNode* root, stack<TreeNode*>& tree)
    {
        while (root != NULL) {
            tree.push(root);
            root = root->left;
        }
    }
    vector<int> inorderTraversal(TreeNode* root)
    {
        vector<int> ans;
        stack<TreeNode*> tree;
        putAllLeft(root, tree); // 找最左边的叶子节点
        TreeNode* top;
        while (!tree.empty()) {
            top = tree.top();
            ans.push_back(top->val); // 访问左(根)节点
            tree.pop();
            putAllLeft(top->right, tree); // 访问右节点
        }
        return ans;
    }
};

本代码提交AC,用时0MS。
调试的时候可以使用我之前写过的由数组生成二叉树的函数,挺方便的。

LeetCode Restore IP Addresses

93. Restore IP Addresses

Given a string containing only digits, restore it by returning all possible valid IP address combinations.

Example:

Input: "25525511135"
Output: ["255.255.11.135", "255.255.111.35"]

本题需要给一个数字字符串添加点号,使其成为一个合法的IP地址。
递归求解。首先设置两个数组kMinLen和kMaxLen,表示剩余字符串的最小/大长度,比如开始时step=0,则字符串的长度范围应该是[4,12]。

const vector<int> kMinLen = { 4, 3, 2, 1 };
const vector<int> kMaxLen = { 12, 9, 6, 3 };

然后递归的切当前字符串最多前3个字符,转成int,看是否<=255,如果是,则继续递归。直到step==4且没有剩余字符时,得到一个合法的IP地址。

完整代码如下:

const vector<int> kMinLen = { 4, 3, 2, 1 };
const vector<int> kMaxLen = { 12, 9, 6, 3 };
class Solution {
public:
    void work(vector<string>& ans, string cand, string left, int step)
    {
        if (step == 4) {
            if (left.size() == 0) {
                ans.push_back(cand);
            }
            return;
        }
        if (left.size() >= kMinLen[step] && left.size() <= kMaxLen[step]) {
            int border = min(3, int(left.size()));
            for (int l = 1; l <= border; l++) {
                string part_str = left.substr(0, l);
                if (part_str != "0" && part_str[0] == ‘0’)
                    continue; // 防止"010"这样的leading zero
                int part_int = atoi(part_str.c_str());
                if (part_int <= 255) {
                    string tmp = cand + left.substr(0, l);
                    if (step < 3)
                        tmp += ".";
                    work(ans, tmp, left.substr(l), step + 1);
                }
            }
        }
    }
    vector<string> restoreIpAddresses(string s)
    {
        vector<string> ans;
        string cand = "";
        work(ans, cand, s, 0);
        return ans;
    }
};

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

二刷。使用数组保存ip地址更方便,代码如下:

class Solution {
public:
	void dfs(vector<string>& ans,vector<string>& cand, string &s, int idx) {
		if (cand.size() == 4 && idx == s.size()) {
			string ip = "";
			for (int i = 0; i < cand.size(); ++i) {
				ip = ip + cand[i] + ".";
			}
			ans.push_back(ip.substr(0, ip.size() - 1));
			return;
		}
		if (cand.size() == 4 && idx < s.size())return;
		if (cand.size() < 4 && idx >= s.size())return;
		for (int i = idx; i < s.size() && i < idx + 3; ++i) {
			string part = s.substr(idx, i - idx + 1);
			if (part[0] == '0'&&part.size() > 1)continue;
			int val = stoi(part);
			if (val <= 255) {
				cand.push_back(part);
				dfs(ans, cand, s, i + 1);
				cand.pop_back();
			}
		}
	}
	vector<string> restoreIpAddresses(string s) {
		vector<string> ans;
		vector<string> cand;
		dfs(ans, cand, s, 0);
		return ans;
	}
};

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

LeetCode Subsets II

90. Subsets II

Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.

Example:

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

求一个“集合”的所有子集,和LeetCode Subsets的区别是本题中,原始“集合”可能包含重复元素,和LeetCode Permutations II很类似。
只需在LeetCode Subsets的基础上增加一个判断,如果在递归的时候发现某个元素和上一个元素相同,则不添加,即不再以该元素独立新开一个递归下去。
举个例子,样例中的{1,2,2},第一次递归的时候,我们会尝试分别以1,2和2递归,但是发现第二个2和前一个元素2相同,所以我们不再以第二个2开一个新的递归下去。但是我们在以第一个2开递归的时候,发现后面有一个2时,并不阻止这个2加到原有的集合中去,也就是子集{2,2}还是会产生的。
完整代码如下:

class Solution {
public:
    void work(vector<vector<int> >& ans, vector<int>& cand, vector<int>& nums, int step)
    {
        ans.push_back(cand);
        for (int i = step; i < nums.size(); i++) {
            if (i != step && nums[i] == nums[i – 1]) { // caution:i != step
                continue;
            }
            cand.push_back(nums[i]);
            work(ans, cand, nums, i + 1);
            cand.pop_back();
        }
    }
    vector<vector<int> > subsetsWithDup(vector<int>& nums)
    {
        vector<vector<int> > ans;
        vector<int> cand;
        sort(nums.begin(), nums.end()); // 先对原数组排序
        work(ans, cand, nums, 0);
        return ans;
    }
};

注意判断的条件是i!=step,而不是i!=0,因为如果是i!=0,则{2,2}这种情况也会被禁止掉。
本代码提交AC,用时6MS。

LeetCode Subsets

78. Subsets

Given a set of distinct integers, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.

Example:

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

求一个集合的所有子集,高中就知道所有子集个数为$2^n$个,包括空集和它本身。
算法思路和LeetCode Combinations几乎一样,只不过成功条件不再是candidate中有k个数了,而是每添加一个元素都算成功一个。

完整代码如下:

class Solution {
public:
    void work(vector<vector<int> >& ans, vector<int>& cand, vector<int>& nums, int step)
    {
        ans.push_back(cand);
        for (int i = step; i < nums.size(); i++) {
            cand.push_back(nums[i]);
            work(ans, cand, nums, i + 1);
            cand.pop_back();
        }
    }
    vector<vector<int> > subsets(vector<int>& nums)
    {
        vector<vector<int> > ans;
        vector<int> cand;
        work(ans, cand, nums, 0);
        return ans;
    }
};

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

LeetCode Combinations

77. Combinations

Given two integers n and k, return all possible combinations of k numbers out of 1 … n.

Example:

Input: n = 4, k = 2
Output:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

求1~n的所有k组合数,和求排列数有点类似。当然也是递归的思路,每个数都有取和不取两种情况,分别递归。唯一需要注意的是递归遍历的时候不走回头路,这样才不会重复。比如求1,2,3,4的两数组合,第一个取1,第二个可以取2,3,4;当第一个取2时,就不再回过头来测试1了,因为之前测试1时,已经产生了{1,2}这个组合,现在测试2时,只需要往前测试3,4了。 完整代码如下:

class Solution {
public:
    void work(vector<vector<int> >& ans, vector<int>& candidate, int step, int& n, int& k)
    {
        if (candidate.size() == k) {
            ans.push_back(candidate);
        }
        else {
            for (int i = step; i <= n; i++) { // 从第step步开始
                candidate.push_back(i);
                work(ans, candidate, i + 1, n, k); // 下一步从i+1步开始
                candidate.pop_back();
            }
        }
    }
    vector<vector<int> > combine(int n, int k)
    {
        vector<vector<int> > ans;
        vector<int> cand;
        work(ans, cand, 1, n, k);
        return ans;
    }
};

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

二刷。还有一种解法,对于每个数,有两种选择,要么拿要么不拿,代码如下:

class Solution {
public:
	void dfs(vector<vector<int>> &ans, vector<int> &cand, int n, int k) {
		
		if (cand.size() == k) {
			ans.push_back(cand);
			return;
		}
		if (n == 0)return;

		cand.push_back(n);
		dfs(ans, cand, n - 1, k);
		cand.pop_back();

		dfs(ans, cand, n - 1, k);
	}
	vector<vector<int>> combine(int n, int k) {
		vector<int> cand;
		vector<vector<int>> ans;
		dfs(ans, cand, n, k);
		return ans;
	}
};

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