暴力递归

暴力递归是动态规划的基础。

暴力递归其实就是尝试。

暴力递归的过程:

1. 把问题转化为规模缩小了的同类问题的子问题;

2. 有明确的不需要继续递归的条件(base case);

3. 有当得到子问题的结果后的决策过程;

4. 不记录每一个子问题的解。

 

例题:

1. 汉诺塔问题:打印n层汉诺塔从最左边移动到最右边的全部过程。


void func(int rest,int down,string from,string help,string to){
    if(rest <= 1){
        cout <<" move " << down << " from " << from << " to " << to << endl;
        return;
    }
    //将除了最底层以外的圆盘移动到中间
    func(rest - 1, down - 1,from,to,help);
    //将最底层的圆盘移动到右边
    func(1,down,from,help,to);
    //将其余圆盘移动回左边
    func(rest - 1, down - 1,help,from,to);

}

void hanoi(int n){
    if(n > 0){
        func(n,n,"left","mid","right");
    }
}

2. 打印一个字符串的全部子序列,包括空字符串。

//打印字符串的全部子序列
//通过完全二叉树(一层代表一个字符),每个路径都选择是否要该字符
//每到达一个叶子节点就是一个子序列
void func(char chs[],string s,int n,int i){
    if(i >= n){
        cout << s << endl;
        return;
    }
    string str(s);
    str += chs[i];
    func(chs,str,n,i + 1);
    func(chs,s,n,i + 1);
}
//节省空间的方法
void func(string chs,int n,int i){
    if(i == n){
        cout << chs << endl;
        return;
    }

    func(chs,n,i + 1);
    char temp = chs[i];
    chs.erase(i - (n - chs.size()),1);
    func(chs,n,i + 1);
    chs.insert(i - (n - chs.size()),&temp);
}

void printStr(string str,int n){
    if( n <= 1){
        return;
    }
//    string s;
//    func(str,s,n,0);
    func(str,n,0);
}

3. 打印一个字符串的全部排列;

    打印一个字符串的全部排列,要求不出现重复的过程。

//打印一个字符串的全部排列
void func(string s,int i){
    if(i == s.size()){
        cout << s << endl;
        return;
    }
    for(int j = i; j < s.size(); j++){
        swap(s[i],s[j]);
        func(s,i + 1);
        swap(s[i],s[j]);
    }
}
//不重复的打印一个字符串的全部排列
//这里有两种方法,一种是在上个方法的基础上,对结果进行清洗
//另一种是在排列过程中进行分支限界,推荐后者。
void  func_2(string s,int i){
    if(i == s.size()){
        cout << s << endl;
        return;
    }
    bool res[26] = {false};
    for(int j = i; j < s.size(); j++){

        if(!res[(int)(s[j] - 'a')]){
            res[(int)(s[j] - 'a')] = true;
            swap(s[i],s[j]);
            func_2(s,i + 1);
            swap(s[i],s[j]);
        }
    }
}
void printPermutation(string s)
{
    if(s.size() <= 0){
        return;
    }
    func_2(s,0);
    return;
}

4. 给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸 牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A 和玩家B都绝顶聪明。请返回最后获胜者的分数。

【举例】 arr=[1,2,100,4]。 开始时,玩家A只能拿走1或4。如果开始时玩家A拿走1,则排列变为[2,100,4],接下来 玩家 B可以拿走2或4,然后继续轮到玩家A... 如果开始时玩家A拿走4,则排列变为[1,2,100],接下来玩家B可以拿走1或100,然后继 续轮到玩家A... 玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1, 让排列变为[2,100,4],接下来玩家B不管怎么选,100都会被玩家 A拿走。玩家A会获胜, 分数为101。所以返回101。 arr=[1,100,2]。 开始时,玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会获胜, 分数为100。所以返回100。


//玩纸牌
int second(int arr[],int L,int R);
int first(int arr[],int L,int R);


int win(int arr[],int n){
    if(arr == NULL || n <= 0){
        return 0;
    }
    return max(first(arr,0,n - 1),second(arr,0,n - 1));
}

//先手
int first(int arr[],int L,int R){
    if( L == R){
        return arr[L];
    }
    return max(arr[L] + second(arr,L + 1,R),arr[R] + second(arr,L,R - 1));
}

//后手
int second(int arr[],int L,int R){
    if(L == R){
        return 0;
    }
    return min(first(arr,L + 1, R),first(arr,L,R - 1));
}

5. 给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能使用递归函数。 如何实现?

/逆序一个栈,不能使用额外的数据结构,只能使用递归
int getStackButton(stack<int>& s){
    int res = s.top();
    s.pop();
    if(s.empty()){
        return res;
    }
    int last = getStackButton(s);
    s.push(res);
    return last;
}

void reverseStack(stack<int>& s){
    if(s.empty()){
        return;
    }
    int num = getStackButton(s);
    reverseStack(s);
    s.push(num);
}

6. 规定1和A对应、2和B对应、3和C对应... 那么一个数字字符串比如"111",就可以转化为"AAA"、"KA"和"AK"。 给定一个只有数字字符组成的字符串str,返回有多少种转化结果。

//6. 规定1和A对应、2和B对应、3和C对应...
// 那么一个数字字符串比如"111",就可以转化为"AAA"、"KA"和"AK"。 给定一个只有数字字符组成的字符串str,返回有多少种转化结果。
int func(int arr[],int n,int i){
    if( i == n){
        return 1;
    }
    if(arr[i] == 0){
        return 0;
    }
    if(arr[i] == 1){
       int res = (i + 1 < n) ? func(arr,n,i + 1) + func(arr,n,i + 2) : func(arr,n,i + 1);
       return res;
    }
    else if(arr[i] == 2){
        int res = func(arr,n,i + 1);
        if(i + 1 < n && arr[i + 1] <= 6 ){
            res += func(arr,n, i + 2);
        }
        return res;
    }
    int res = func(arr,n,i + 1);
    return res;
}

7. 给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表 i号物品的重量和价值。给定一个正数bag,表示一个载重bag的袋子,你装的物 品不能超过这个重量。返回你能装下最多的价值是多少?

int func(int weight[],int values[],int n,int i,int alreadyWeight,int bagWeight,int value){
    if( alreadyWeight > bagWeight ){
        return 0;
    }
    if(i == n){
        return value;
    }
    int res_1 = func(weight,values,n,i + 1,alreadyWeight,bagWeight,value);
    int res_2 = func(weight,values,n, i + 1,alreadyWeight + weight[i],bagWeight,value + values[i]);
    return max(res_1,res_2);
}
int maxValues(int weight[],int values[],int n,int bagWeight){
    return func(weight,values,n,0,0,bagWeight,0);
}

8. N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列, 也不在同一条斜线上。 给定一个整数n,返回n皇后的摆法有多少种。 n=1,返回1。 n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0。 n=8,返回92。

//n皇后问题
bool isValid(int* record,int i,int j){
    for(int k = 0; k < i; k++){
        if(record[k] == j || abs(record[k] - j) == abs(i - k)){
            return false;
        }
    }
    return true;
}


int func(int n,int i,int* record){
    if( i == n ){
        return 1;
    }
    int res = 0;
    for(int j = 0; j < n; j++){
        if(isValid(record,i,j)){
            record[i] = j;
            res += func(n, i+ 1,record);
        }
    }
    return res;
}

int nQueens(int n){
    if( n <= 1){
        return 1;
    }
    int record[10];
    return func(n,0,record);
}

 

### Java 实现从暴力递归到动态规划的优化 #### 暴力递归实现 对于某些问题,如零钱兑换问题,在最初阶段可以采用暴力递归的方法解决。然而这种方法存在大量重复计算的问题,效率低下。 ```java public int coinChange(int[] coins, int amount) { if (amount < 0) { return -1; } if (amount == 0) { return 0; } int minCount = Integer.MAX_VALUE; for (int i = 0; i < coins.length; i++) { int res = coinChange(coins, amount - coins[i]); if (res >= 0 && res < minCount) { minCount = 1 + res; } } return minCount == Integer.MAX_VALUE ? -1 : minCount; } ``` 此代码展示了最基础的递归逻辑[^1]。当金额 `amount` 小于零返回 `-1` 表示无法组合;等于零表示正好匹配成功,不需要任何硬币参与运算。遍历每种面额尝试减少当前总金额并递归求解剩余部分所需的最少数量直到找到最小值或者确认无解为止。 #### 添加记忆化搜索(Memoization) 为了改善上述算法性能,可以在原有基础上引入缓存机制保存已经计算过的结果防止不必要的重复工作: ```java private static final Map<Integer, Integer> memo = new HashMap<>(); public int coinChangeWithMemo(int[] coins, int amount) { if (amount < 0) { return -1; } if (amount == 0) { return 0; } // Check cache first before computing. if (!memo.containsKey(amount)) { int minCount = Integer.MAX_VALUE; for (int i = 0; i < coins.length; ++i) { int res = coinChangeWithMemo(coins, amount - coins[i]); if (res >= 0 && res < minCount) { minCount = 1 + res; } } memo.put(amount, (minCount == Integer.MAX_VALUE) ? -1 : minCount); } return memo.get(amount); } ``` 这段改进后的版本利用哈希表作为外部存储器记录之前遇到过的输入及其对应输出以便快速检索已知答案从而大大减少了时间复杂度[^3]。 #### 完全转换成自底向上的动态规划方案 最终目标是从完全依赖函数调用来构建表格形式的数据结构逐步填充直至获得全局最优解的过程称为“自底向上”。这种方式不仅消除了显式的栈空间消耗还进一步提高了程序运行速度。 ```java public int dpCoinChange(int[] coins, int amount) { int max = amount + 1; int[] dp = new int[max]; Arrays.fill(dp, max); dp[0] = 0; for (int i = 1; i <= amount; i++) { for (int j = 0; j < coins.length; j++) { if (coins[j] <= i) { dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); } } } return dp[amount] > amount ? -1 : dp[amount]; } ``` 这里定义了一个长度为 `max` 的数组 `dp` 来保存不同数额所需最少硬币数目,并初始化所有元素为最大整数值代表未知状态。接着按照从小到大顺序更新每一个位置处的最佳选择结果直到处理完毕整个范围内的数据项。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值