332. 重新安排行程
链接
文章链接
题目链接
第一想法
这道题刚开始开的时候没啥思路,之后尝试写了一下,这道题的难点在于如何确定这条边是否已经使用过了(避免循环),所以我尝试用map记录这条边是否使用过,但是太耗费时间了,导致超时了
//以下代码超时 只是为了记录思路
function findItinerary(tickets: string[][]): string[] {
let map:Map<string,number>=new Map()//用于记录这条边是否已经使用
let str=""
let res:string[]=[]
for(let i=0;i<tickets.length;i++){
map.set(tickets[i].join(""),1+(map.get(tickets[i].join(""))||0))
}
const foo=(arr:string[],tickets:string[][])=>{//还是回溯的模版
if(arr.length==tickets.length+1){
let string:string=arr.join("")
if(str&&string>str) return;
str=string
res=arr.slice()
return
}
for(let i=0;i<tickets.length;i++){
if(tickets[i][0]!=arr[arr.length-1]) continue //找到对应的机场 例如目前在JFK机场 那么只能从JFK机场起飞
if(map.get(tickets[i].join(""))!-1<0) continue//防止这条边使用过了但是还是继续使用(防止循环)
map.set(tickets[i].join(""),map.get(tickets[i].join(""))!-1)
arr.push(tickets[i][1])
foo(arr,tickets)
arr.pop()//回溯
map.set(tickets[i].join(""),map.get(tickets[i].join(""))!+1)//回溯
}
}
let arr:string[]=[]
for(let i=0;i<tickets.length;i++){//入口
if(tickets[i][0]!='JFK')continue
map.set(tickets[i].join(""),map.get(tickets[i].join(""))!-1)
arr.push(...tickets[i])//添加两个机场
foo(arr,tickets)
arr.pop()//回溯
arr.pop()//回溯
map.set(tickets[i].join(""),map.get(tickets[i].join(""))!+1)
}
return res
};
看完文章后的想法
文章的思想确实比我的好,文章是通过map<string,map<string,number>>这个结构进行求解的,同时还运用到返回值,因为这道题是找到一个最优解,所以先把tickets排个序,之后局部最优推出全局最优,代码如下:
function findItinerary(tickets: string[][]): string[] {
type tickType={
[index:string]:Map<string,number>
}
let ticketMap:tickType={}//用于记录起点为tickets[i][0]的航班
let res:string[]=['JFK']
tickets.sort((a,b)=>a[1]<b[1]?-1:1) //进行排序 用于找到第一个可行解就是最优解
for(let i=0;i<tickets.length;i++){
if(ticketMap[tickets[i][0]]==undefined){
ticketMap[tickets[i][0]]=new Map()
}
ticketMap[tickets[i][0]].set(tickets[i][1],(ticketMap[tickets[i][0]].get(tickets[i][1])||0)+1)//这里用map记录终点为tickets[i][1]且起点为tickets[i][0]的个数
}
const foo:(res:string[],str:string,ticketMap:tickType)=>boolean=(res,str,ticketMap)=>{
if(res.length==tickets.length+1) return true //找到可行解(也是最优解)
if(ticketMap[str]==undefined) return false //如果ticketMap[str]==undefined说明没有从这个地点起飞的航班 所以直接返回false
for(const [s,num] of ticketMap[str].entries()){
if(num==0) continue //num为0说明这个航班不能再使用了
ticketMap[str].set(s,num-1)
res.push(s)
if(foo(res,s,ticketMap)==true) return true//找到最优解直接返回就可
res.pop() //回溯
ticketMap[str].set(s,num)
}
return false
}
foo(res,'JFK',ticketMap)
return res
};
思考
这道题是一道hard题,思路我认为比较好想,只是需要记录这个航线的个数,防止超过次数使用,同时这道题也很巧妙,通过sort进行排序后找到局部最优就是全局最优,所以提前sort可以帮助我们很快的解决问题.我自己的思路是直接记录from和to这个字符串,但是太慢了,文章这种通过object和map的结合可以很快的找出结果.因此使用这种方法不会超时
51. N 皇后
链接
文章链接
题目链接
第一想法
这道题套模版的话难度就不算太大了,唯一具有难度的是如何判断当前Q的当前行,当前列和斜线上没有其他皇后Q,所以我用了一个dir来判断斜线上是否存在Q:
function solveNQueens(n: number): string[][] {
let res: string[][] = []
let dir: number[][] = [[-1, -1], [-1, 1], [1, -1], [1, 1]]
const foo = (arr: string[], index: number) => { //index来判断到第几行了
if (index == n) { //终止条件
res.push(arr.slice())
return
}
for (let i = 0; i < n; i++) {//因为每一次是一行,所以就不同行判断了
if (arr.find((item) => item[i] == 'Q') != undefined) continue//竖向条件 不在同一竖线上
let x: number = i //行
let y: number = index //列
let boo: boolean = false
//左上斜线条件
while (x >= 0 && y >= 0 && x < n && y < n) {
x += dir[0][0]
y += dir[0][1]
if (arr?.[y]?.[x] == 'Q') {
boo = true
break
}
}
x = i
y = index
//左下斜线条件
while (x >= 0 && y >= 0 && x < n && y < n) {
x += dir[1][0]
y += dir[1][1]
if (arr?.[y]?.[x] == 'Q') {
boo = true
break
}
}
x = i
y = index
//右上斜线条件
while (x >= 0 && y >= 0 && x < n && y < n) {
x += dir[2][0]
y += dir[2][1]
if (arr?.[y]?.[x] == 'Q') {
boo = true
break
}
}
x = i
y = index
//右下斜线条件
while (x >= 0 && y >= 0 && x < n && y < n) {
x += dir[3][0]
y += dir[3][1]
if (arr?.[y]?.[x] == 'Q') {
boo = true
break
}
}
if (boo == true) continue //boo为true说明斜线上存在
let str = ''
for (let j = 0; j < n; j++) {
if (j == i) str += 'Q'
else str += '.'
}
arr.push(str)
foo(arr, index + 1)
arr.pop()
}
}
foo([], 0)
return res
};
看完文章后的想法
文章的想法和我的类似,都是先通过判断是否通过验证来决定是否继续往后递归的,同时文章中的代码更加精简,这张图也能很好的表示出如何剪枝:
文章中就剪枝了左上斜线和右上斜线,因为是从上往下写的,所以后面的可以忽略,精简后的代码如下:
function solveNQueens(n: number): string[][] {
let res: string[][] = []
const foo = (arr: string[], index: number) => {
if (index == n) {
res.push(arr.slice())
return
}
for (let i = 0; i < n; i++) {
if (arr.find((item) => item[i] == 'Q') != undefined) continue//竖向条件 不在同一竖线上
if (isValid(arr, i, index)) continue
let str = ''
for (let j = 0; j < n; j++) {
if (j == i) str += 'Q'
else str += '.'
}
arr.push(str)
foo(arr, index + 1)
arr.pop()
}
}
const isValid = (arr: string[], row: number, col: number) => {
let x: number = row
let y: number = col
//左上斜线条件
while (x >= 0 && y >= 0 && x < n && y < n) {
x += -1
y += -1
if (arr?.[y]?.[x] == 'Q') {
return true
}
}
x = row
y = col
while (x >= 0 && y >= 0 && x < n && y < n) {
x += 1
y += -1
if (arr?.[y]?.[x] == 'Q') {
return true
}
}
return false
}
foo([], 0)
return res
};
思考
这道题之前尝试过,所以有一定的思路,这道题难得就是剪枝,由于是一层填一个,所以天然的解决了同行问题,通过find解决列问题,通过isValid解决斜线问题.这次算是巩固练习.
37. 解数独
链接
文章链接
题目链接
第一想法
没啥想法,原本想的是三个条件用三个数组完成,列不同,行不同,块不同,但是发现写完之后不会递归,递归的不对,就放弃了.
看完文章后的想法
看完文章后,有一种奇奇妙妙的感觉,这是第一回碰见二维数组递归,同时终止条件也不是放在函数开头的题,对于验证函数来说,除了块检查比较需要绕个弯,其他的还好,难在递归函数.递归函数需要判断在哪里写终止条件,代码如下:
function solveSudoku(board: string[][]): void {
const isValid = (row: number, col: number, board: string[][], val: string) => {
//行检查
for (let i = 0; i < 9; i++) {
if (board[row][i] === val) return false
}
//列检查
for (let i = 0; i < 9; i++) {
if (board[i][col] === val) return false
}
//块检查
let r: number = ~~(row / 3) * 3
let c: number = ~~(col / 3) * 3
for (let i = r; i < r + 3; i++) {
for (let j = c; j < c + 3; j++) {
if (board[i][j] === val) return false
}
}
return true
}
const foo: (board: string[][]) => boolean = function () {
for (let i = 0; i < board.length; i++) { //行遍历
for (let j = 0; j < board[i].length; j++) {//列遍历
if (board[i][j] != ".") continue
for (let k = 1; k < 10; k++) {
if (isValid(i, j, board, String(k))) {//判断是否能填数
board[i][j] = String(k)
if (foo(board) == true) return true //填充成功
board[i][j] = "."
}
}
return false //如果for填数遍历失败的话,说明上一层的填数不正确,返回false
}
}
return true //能走到这里说明前面都填满了
}
foo(board)
};
思考
这道题第一次见,看完文章后虽然有了一定的想法,但是写的还是有些吃力的,通过我的第一想法是想复杂了,直接在board上进行遍历就可以了,块遍历比较难想,这道题得多次练习才能熟悉掌握.
今日总结
今日耗时4.3小时,这是耗时最长的一天,因为这三道题确实难度大,得多加练习才能掌握.