回溯算法练习day.4

93.复原ip地址

链接:. - 力扣(LeetCode)

题目描述:

有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

  • 例如:"0.1.2.201" "192.168.1.1"有效 IP 地址,但是 "0.011.255.245""192.168.1.312""192.168@1.1"无效 IP 地址。

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址 ,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

示例 1:

复制代码
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]

示例 2:

复制代码
输入:s = "0000"
输出:["0.0.0.0"]

提示:

  • 1 <= s.length <= 20
  • s 仅由数字组成

思路:

因为是分割问题,因此可以使用回溯算法解决,我们可以将其抽象为一个树形结构

因为IP地址总共只有3个.,因此我们可以使用一个标记来记录逗点的数量,当逗点足够三个时,我们就不再需要向下遍历,因为再向下就已经不是合法的IP地址了,我们在这时候只需要对剩余的字符串进行判断,如果剩余的字符串是合法的,我们就将其进行收集,并插入逗点,这样就得到了一个合法的IP地址

代码如下:

cpp 复制代码
// 记录结果数组
char** result;
// 结果数组的当前元素数量
int resultTop;
// 记录应该加入'.'的位置的数组
int segments[3];

// 判断字符串片段是否有效
int isValid(char* s, int start, int end) {
    // 若起始位置大于结束位置,则不合法
    if(start > end)
        return 0;
    // 如果数字以0开头并且不止一位,则不合法
    if (s[start] == '0' && start != end) {
        return false;
    }
    int num = 0;
    // 遍历字符串片段,将字符转换为数字,并判断是否大于255
    for (int i = start; i <= end; i++) {
        // 遇到非数字字符,则不合法
        if (s[i] > '9' || s[i] < '0') {
            return false;
        }
        num = num * 10 + (s[i] - '0');
        // 如果数字大于255,则不合法
        if (num > 255) {
            return false;
        }
    }
    return true;
}

// 回溯函数,用于生成符合条件的 IP 地址
void backTracking(char* s, int startIndex, int pointNum) {
    // 若'.'数量为3,分隔结束
    if(pointNum == 3) {
        // 若最后一段字符串符合要求,将当前的字符串放入结果数组中
        if(isValid(s, startIndex, strlen(s) - 1)) {
            // 分配临时字符串数组的内存空间,长度为原字符串长度加上最多3个'.'和一个结束符'\0'
            char* tempString = (char*)malloc(sizeof(char) * strlen(s) + 4);
            int j;
            // 记录添加字符时tempString的下标
            int count = 0;
            // 记录添加字符时'.'的使用数量
            int count1 = 0;
            for(j = 0; j < strlen(s); j++) {
                tempString[count++] = s[j];
                // 若'.'的使用数量小于3且当前下标等于'.'下标,添加'.'到数组中
                if(count1 < 3 && j == segments[count1]) {
                    tempString[count++] = '.';
                    count1++;
                }
            }
            tempString[count] = 0;
            // 扩容结果数组,并将临时字符串添加到结果数组中
            result = (char**)realloc(result, sizeof(char*) * (resultTop + 1));
            result[resultTop++] = tempString;
        }
        return ;
    }

    int i;
    // 从起始位置开始遍历字符串
    for(i = startIndex; i < strlen(s); i++) {
        if(isValid(s, startIndex, i)) {
            // 记录应该添加'.'的位置
            segments[pointNum] = i;
            // 递归调用自身,搜索下一个'.'的位置
            backTracking(s, i + 1, pointNum + 1);
        }
        else {
            break;
        }
    }
}

// 主函数,入口点,用于恢复 IP 地址
char ** restoreIpAddresses(char * s, int* returnSize){
    // 分配结果数组的初始内存空间
    result = (char**)malloc(0);
    resultTop = 0;
    // 调用回溯函数,生成符合条件的 IP 地址
    backTracking(s, 0, 0);
    // 将结果数量存储到returnSize指针指向的变量中
    *returnSize = resultTop;
    // 返回结果数组
    return result;
}

78.子集

链接:. - 力扣(LeetCode)

题目描述:

给你一个整数数组 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 中的所有元素 互不相同

思路:

因为这题也是求组合问题,因此还是使用回溯算法解决,我们将其抽象为树形结构

就可以发现,树的每个节点都是我们需要收集的子集,注意,我们在第一条支路中取了1,在后边就不需要的取1了,因此第一条支路向下递归搜索取能够取到12,13等,如果在后面的支路中再取前面取过的元素,就会出现21,31等,因为是组合,因此这两个是重复的,所以不需要再进行取值

回溯实现:

1.确定函数参数和返回值:回溯返回值一般为空,传入的参数应该为题目提供的集合以及我们每次遍历的开始位置

2.确定函数的终止条件:当我们每条支路的开始遍历位置已经为空时,即我们已经遍历到了末尾,就到达了叶子节点,就进行返回

3.确定单层递归逻辑,收集路径下的节点,再向下递归,之后再进行回溯,遍历另一条支路

代码实现:

cpp 复制代码
int *path;               // 存储当前路径的数组
int pathtop;             // 当前路径的顶部索引

int **result;            // 存储所有子集的数组
int resulttop;           // 存储数组的顶部索引

int *len;                // 存储每个子集的长度

void copy()
{
    int *temp = (int *)malloc(sizeof(int) * pathtop);  // 临时数组,用于复制当前路径
    for(int i = 0; i < pathtop; i++)                   // 复制当前路径到临时数组
        temp[i] = path[i];
    result = (int **)realloc(result, sizeof(int *) * (resulttop + 1)); // 重新分配存储子集的数组大小
    len = (int *)realloc(len, sizeof(int) * (resulttop + 1)); // 重新分配存储子集长度的数组大小
    len[resulttop] = pathtop;                          // 存储当前子集的长度
    result[resulttop++] = temp;                        // 将当前子集添加到结果数组中
}

void backtracking(int *nums, int numsSize, int startindex)
{
    copy();                                            // 复制当前路径到结果数组中

    if(startindex >= numsSize )
        return;

    for(int i = startindex; i < numsSize; i++)
    {
        path[pathtop++] = nums[i];                    // 将当前元素添加到路径中
        backtracking(nums, numsSize, i+1);             // 递归调用,查找以当前元素开头的所有子集
        pathtop--;                                     // 回溯,移除最后添加的元素
    } 
}

int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
    path  = (int *)malloc(sizeof(int) * numsSize);     // 初始化路径数组
    result = (int **)malloc(sizeof(int *));            // 初始化存储子集的数组
    len = (int *)malloc(sizeof(int) * 1000);           // 初始化存储子集长度的数组
    resulttop = pathtop = 0;                           // 初始化路径顶部索引和结果顶部索引为0

    backtracking(nums, numsSize, 0);                   // 开始回溯查找所有子集

    *returnSize = resulttop;                           // 设置返回的子集个数
    *returnColumnSizes = (int *)malloc(sizeof(int) * resulttop); // 为每个子集的长度分配内存
    for(int i = 0; i < resulttop; i++)
        (*returnColumnSizes)[i] = len[i];              // 存储每个子集的长度
    
    return result;                                     // 返回所有子集数组
}

90.子集II

链接:. - 力扣(LeetCode)

题目描述:

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的

子集

(幂集)。

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

示例 1:

复制代码
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

示例 2:

复制代码
输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10

思路:

该题与上题一样,也是求组合问题,但是这个题要求不能包含重复子集,因为我们要进行去重操作,可以考虑到使用回溯算法解决,所以可以抽象为一个树形结构

我们可以看出,当我们横向出现重复的元素时,得到的结果就会重复,因此就需要在横向进行去重操作,而在纵向,因为我们取出的是集合中不同位置的元素,因此就不需要去重,在这里,我们收集的也是每个节点的结果,只是增加了去重的操作

回溯实现:

1.确定函数的参数和返回值,参数应该为题目提供和集合和开始遍历的位置

2.确定终止条件,当我们遍历到叶子节点,即开始位置已经是集合末尾时,退出

3.确定单层递归逻辑,当我们在遍历时,只要横向不重复,就进行收集路径,并进行递归和回溯,如果发现重复,则跳过

注意:我们应该在终止条件之前进行结果的收集,因为那是新的递归遍历的开始,每次开始前我们都应该对上次递归的结果进行收集,因为上次的结果是我们的节点

代码实现:

cpp 复制代码
/**
 * 返回大小为 *returnSize 的数组的数组。
 * 数组的大小作为 *returnColumnSizes 数组返回。
 * 注意:返回的数组和 *columnSizes 数组都必须是通过 malloc 分配的,假设调用者会调用 free()。
 */

int *path; // 路径数组
int pathtop; // 路径长度

int **result; // 结果数组
int resulttop; // 结果数组的长度

int *len; // 每个子集的长度数组

// 比较函数,用于排序数组
int cmp(const void *a, const void *b)
{
    return *((int *)a) - *((int *)b);
}

// 复制当前路径并添加到结果数组中
void copy(void)
{
    int *temp = (int *)malloc(sizeof(int) * pathtop);
    for (int i = 0; i < pathtop; i++)
        temp[i] = path[i];
    result = (int **)realloc(result, sizeof(int *) * (resulttop + 1));
    len = (int *)realloc(len, sizeof(int) * (resulttop + 1));

    len[resulttop] = pathtop;
    result[resulttop++] = temp;
}

// 回溯函数,用于递归生成所有子集
void backtracking(int *nums, int numsSize, int startindex, int *used)
{
    copy(); // 复制当前路径

    if (startindex >= numsSize)
        return;

    for (int i = startindex; i < numsSize; i++)
    {
        if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0)
            continue; // 避免重复元素

        path[pathtop++] = nums[i];
        used[i] = 1;
        backtracking(nums, numsSize, i + 1, used);
        used[i] = 0;
        pathtop--;
    }
}

// 主函数,生成所有不重复的子集
int **subsetsWithDup(int *nums, int numsSize, int *returnSize, int **returnColumnSizes)
{
    path = (int *)malloc(sizeof(int) * numsSize);
    len = (int *)malloc(sizeof(int) * 1000);
    int *used = (int *)malloc(sizeof(int) * numsSize);

    pathtop = resulttop = 0;
    qsort(nums, numsSize, sizeof(int), cmp); // 排序输入数组

    backtracking(nums, numsSize, 0, used); // 从第一个元素开始生成子集

    *returnSize = resulttop;
    *returnColumnSizes = (int *)malloc(sizeof(int) * resulttop);
    for (int i = 0; i < resulttop; i++)
        (*returnColumnSizes)[i] = len[i];

    return result; // 返回生成的子集数组
}
相关推荐
自由的dream32 分钟前
0-1背包问题
算法
2401_8572979138 分钟前
招联金融2025校招内推
java·前端·算法·金融·求职招聘
良月澪二2 小时前
CSP-S 2021 T1廊桥分配
算法·图论
wangyue43 小时前
c# 线性回归和多项式拟合
算法
&梧桐树夏3 小时前
【算法系列-链表】删除链表的倒数第N个结点
数据结构·算法·链表
QuantumStack3 小时前
【C++ 真题】B2037 奇偶数判断
数据结构·c++·算法
今天好像不上班3 小时前
软件验证与确认实验二-单元测试
测试工具·算法
wclass-zhengge4 小时前
数据结构篇(绪论)
java·数据结构·算法
何事驚慌4 小时前
2024/10/5 数据结构打卡
java·数据结构·算法
结衣结衣.4 小时前
C++ 类和对象的初步介绍
java·开发语言·数据结构·c++·笔记·学习·算法