代码随想录笔记-回溯算法
三大步骤:递归参数、终止条件、循环内
其他细节:
(1):树的同一层待取元素与结果路径中重复元素判别和舍弃
方法一:和数组的前一个元素对比
方法二:使用unset数组存储元素,并和之前的进行比对
方法三:用哈希数组(若记录元素的值可能为负数,需要+a来进行位置偏移)
(2):树的纵向探索中,元素的筛选
方法一:
(3):递归前添加元素、递归i+1个元素失败&回溯&pop第i个元素&for中i++(若是最后一个元素,for中条件不满足直接失败,当前函数执行结束进行回溯,pop第i-1个元素,for中为第i-1+1,即结果队列先删除最后一个后删除倒数第一个最后横向添加最后一个,然后递归犯贱一样的又对第i个元素进行i+1的插入试探,最后失败返回、pop第i-1+1个元素,当前函数执行结束回溯上一层,pop第i-2个元素,插入第i-2+1个元素、递归第i-2+1+1个元素、犯贱成功)、即结果队列先删除倒数第三个后续又横向插入倒数第二个递归插入最后一个
(4):有条件返回和无条件返回结果
(5):第一个结果集得到后,第二个结果集就是递归失败返回第i层pop(i),正常结束返回第i-1层pop(i-1),横向插入i-1+1
(6):for中i=0和i=index的区别:
(7):continue在递归主循环for中的作用:
(8):判断同一列中是否和之前有重复用used[i]=true,判断同一层中是否和之前有重复,且之前的是用过的用used[i-1]=false&&i>0&&nums[i]==nums[i-1](因为同一层的判断一定是回溯回来后的判断,所有的true都恢复为false)(非树首元素,当前元素和候选集的上一个元素相同)
(2)pop(i)后,当前for中的i没有变化,i找到pop掉的元素的下一个元素
子集问题1(源数组不含重复元素):
子集问题2(源数组包含重复元素):
全排列问题1(源无重复,结果自然没有重复)
特点1:for中i为0,递归中不负责推进i,第一次递归依靠纵向去重,逼迫横向for移动来推进i(设置use[i]==1 continue,跳出循环,不进行后续设置递归恢复操作,直接for的i++)
全排列问题2(源有重复,结果不能有重复) :
特点1:for中i为0,递归中不负责推进i,if(i>0&&......)continue,由于i=0执行不了continue,也无法推进i,所以推进的重任给了包裹递归的if(used[i]==false判断),第一次i=0,used[0]标记为true,第二次i=0,包裹if判断失败,不进入设置递归恢复过程,正常结束函数,返回上一层
特点2:判断同层重复(非首元素的当前待选择元素和候选数组中的前一个元素进行对比)(横向使用过,used[i-1]一定是false,因为需要同层对比往往是已经有一个结果集,回溯过程中used会被恢复为false)for中初始i=0、递归出口path.size==nums.size(需要遍历整个数组)
疑惑:为啥要判断是否使用过,想通了!事实上,同一个纵向结果集,used[i-1]是true,因为还没有经历回溯的过程,同一层的used[i-1]是false,因为回溯后used[i-1]为false,所以不判断used[i-1]也可以
n后问题:
特点:纵向遍历抽象为纵向递归、横向遍历抽象为for循环,for中初始i=0,递归参数(row+1),递归出口(row==n)也就是限制了递归的纵向深度,每一次都是row达到n后才结束递归
数独问题:
特点:改变纵向递归横向for的习惯,纵向和横向的遍历都用for替代,递归负责将填入数字的新表格带入新函数的判断和插入环节,if中的递归(要在递归函数前获取false或者true,但是这题有更好的思路,若当前位置所有候选元素逻辑不符合说明之前位置元素填的有问题则跳出包含递归函数的if,返回false,回溯board[i][j]='.',对于上一层函数而言,接受if中递归返回的是false,不执行return true,执行前一个位置将填写数字置空,尝试其他元素,经历所有位置元素的填写悔改......,最终所有位置来回n次后,无错误的跳出了两个for循环,代表已经试错完毕,得到唯一解,返回true,返回上一层,上一层的if(递归函数)得到true,执行return true,返回上一层,如此if (true),true返回给上一层if,......,直到true返回给最初层函数,结束函数)
总结:
1.对于一维数组问题,两种推进i的方式,递归推进:for中i为index,index在递归函数参数中index+1,for推进:用if判断遍历过的元素,包裹递归函数,重复遍历时自动结束当前函数,返回上一层,或者遍历函数前,用if判断遍历过的元素并用continue直接跳过再次递归函数,正常结束,返回上一层,执行for中的i++
2.若是二维地图类,也有两种遍历方式
方案一:两个for,递归不需要row+1
方案二:一个for,递归需要row+1
3.若for中i为0,那么需要对处理过的元素进行标记,多一次无效递归,再次递归前continue跳过或者用标记判断包裹再次递归从而判定失败,正常结束无效递归,返回上一层,for中i++推进遍历
4.多结果集(n后问题)和唯一结果集(数独问题),若结果集需要用 result.push(path),然后return,回溯,唯一结果集
5.从根节点到叶子节点(全排列)和根节点到中间节点(递增子序列)实现不同:递归出口和结果集的添加(结果集添加,全路径是第一次遍历到底的路径,以及回溯后再次遍历到底的路径,回溯一半的路径中间节点个数不够,if(path.size==size)限制添加到结果集,且if中有return;子路径,只要if)
6.只要for中有i<num.size,无论是排列问题还是子集组合问题,第一个完整路径(子集类似于先序,遍历一个新节点就有结果:1,(1,2),(1,2,3))一定会因为for中限制,返回i层pop掉i,然后正常结束i层,返回i-1层,pop掉i-1