算法训练营(九)分治、回溯的实现和特性

目录

一、基础

1.1 递归状态树

1.2 分治代码模板

1.3 复习递归代码模板

1.4 回溯的概念

二、实战习题

2.1 Pow(x, n)

2.2 子集

三、实战题目

3.1  多数元素

3.2 电话号码的字母组合

3.3 N 皇后


一、基础

1.1 递归状态树

 

 

1.2 分治代码模板

分治代码与递归代码模板的区别:多了一步将结果合并(merge)

int divide_conquer(Problem* problem, int params) {
	// recursion terminator
	if (problem == nullptr) {
		process_result
		return return_result;
	}

	// process current problem
	subproblems = split_problem(problem, data)
	subresult1 = divide_conquer(subproblem[0], p1)
	subresult2 = divide_conquer(subproblem[1], p1)
	......

	// merge
	result = process_result(subresult1, subresult2, subresult3)

	// revert the current level status

	return 0;

}

 

1.3 复习递归代码模板

// C/C++
void recursion(int level, int param) { 
 
   // recursion terminator
   if (level > MAX_LEVEL) { 
       // process result 
       return ; 
   }
 
   // process current logic 
   process(level, param);
 
   // drill down 
   recursion(level + 1, param);
 
   // reverse the current level status if needed
 
}

 

 

 

1.4 回溯的概念

回溯法采用试错的思想,它尝试分步的去解决一个问题。

在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。

回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:

  • 找到一个可能存在的正确的答案;
  • 在尝试了所有可能的分步方法后宣告该问题没有答案。

在最坏的情况下,回溯法会导致次复杂度为指数时间的计算。

 

 

二、实战习题

 

2.1 Pow(x, n)

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

思路

拿到面试题后,先与面试官明确题意,数据边界。比如n是否可以为0,是否可以为负数,n的取值范围是多少等。

可以使用分而治之的思路进行求解。

class Solution {
    public double myPow(double x, int n) {
        double res = 1.0;
        for(int i = n; i != 0; i /= 2){
            if(i % 2 != 0){
                res *= x;
            }
            x *= x;
        }
        return  n < 0 ? 1 / res : res;
    }
}

 

 

 

2.2 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

思路:

观察一下数组nums。可以得出如下规律

nums长度排列组合结果元素个数
0[]1
1[]                          [1]2
2[]  [1]                    [2]  [1,2]4
3[] [1] [2] [1,2]        [3] [1,3] [2,3] [1,2,3]8

 

nums中每增加一个元素,子集的个数 = 增加元素之前的子集 + 新增元素与旧子集中每个元素进行组合 

因此可以得出如下答案:

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>>res;
        vector<int>temp;
        res.push_back(temp);

        //有几个元素就会有2的n次幂的结果
        for(auto i : nums){
            int size = res.size();
            for(int j=0; j< size;j++){
                temp = res[j];
                temp.push_back(i);
                res.push_back(temp);
            }
        }
        return res;
    }
};

一遍ac,舒服~

 

 

 

三、实战题目

 

 

 

3.1  多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:[3,2,3]
输出:3

示例 2:

输入:[2,2,1,1,1,2,2]
输出:2

进阶:

  • 尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

思路:

超过半数的那个数的个数一定大于数组长度的一半(这不是废话吗)。所以,用两个变量res和time,res代表当前出现次数最多的数,time记录该数出现的次数。遍历数组

  • 如果当前元素与res的数值不同,则将time-1;
  • 若相同,则time+1
  • 若不同,time<=0,说明当前res记录的数并非是我们想要的结果。此时将当前遍历的值作为出现次数最多的数,并更新time的值为1;

从而有如下答案:

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int res=nums[0];
        int time=1;
        for(int i=0; i<nums.size();i++){
            if(nums[i]!= res){
                time--;
                if(time<=0){
                    res=nums[i];
                    time=1;
                }
            }else{
                time++;
            }
        }
        return res;
    }
};

 

 

 

3.2 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。
class Solution {
public:
    const vector<string> keyboard{"", "", "abc", "def", 	// '0','1','2',...
    	"ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" };

    vector<string> letterCombinations (const string &digits) {
        vector<string> result;
        if (digits == "") return result;
        dfs(digits, 0, "", result);
        return result;
    }

    void dfs(const string &digits, size_t cur, string path,
            vector<string> &result) {
        if (cur == digits.size()) {
            result.push_back(path);
            return;
        }
        for (auto c : keyboard[digits[cur] - '0']) {
            dfs(digits, cur + 1, path + c, result);
        }
    }
};

 

 

 

3.3 N 皇后

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

 

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

 

难题,先放放。。。。脑瓜疼

 

 

 

扩展阅读:

牛顿迭代法——http://www.matrix67.com/blog/archives/361

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值