剑指offer JavaScript编程

192 阅读10分钟

思路来源:b站up主:【香辣鸡排蛋包饭】。本博只是自我学习整理所用
主页: space.bilibili.com/363956974?s…

1.二维数组的查找

function Find(target, array)
{
    if (array.length < 0) return false;
    let row = array.length;
    let col = array[0].length;
    let [i, j]=[0, col -1];
    for(i,j ; j>=0 && i<row;){
        if(array[i][j] == target) return true;
        if(array[i][j] > target){
            j--;
            continue;
        }
        if(array[i][j] < target){
            i++;
            continue;
        }
    }
    return false;
}

2.替换空格

function replaceSpace( s ) {
    if(!s){
        return ''
    }
    let spacecount = 0;
    let letterArr = [];//将字符串放到数组里
    for(let i = 0; i < s.length; i++){
        if(s[i] === ' '){
            spacecount++;
        }
         letterArr.push(s[i]);
    }
    
    let p1 = letterArr.length - 1;
    let p2 = letterArr.length + 2 * spacecount - 1;//定义两个指针
    while(p1<p2){
        if(letterArr[p1] === ' '){
            letterArr[p2--] = '0';
            letterArr[p2--] = '2';
            letterArr[p2--] = '%';
            p1--;
        }else{
            letterArr[p2] = letterArr[p1];
            p1--;
            p2--;
        }
    }
    return letterArr.join('');
}
module.exports = {
    replaceSpace : replaceSpace
};
function replaceSpace( s ) {
    return s.replace(/\s/g, '%20');
}
module.exports = {
    replaceSpace : replaceSpace
};

3.从尾到头打印链表

function printListFromTailToHead(head)
{
    // write code here
    var str=[];
    while(head != null){
        str.unshift(head.val);
        head = head.next;
    }
    return str;
}
module.exports = {
    printListFromTailToHead : printListFromTailToHead
};
function printListFromTailToHead(head)
{
    // write code here
    //一直递归到链表尾部,从尾部往前将值放进数组,放进数组的方式是从尾部添加(push)
    var arr=[];
    getArr(head,arr);
    return arr;
}

function getArr(head,arr){
    if(head!==null){
        getArr(head.next, arr);
        arr.push(head.val);
    }
}
module.exports = {
    printListFromTailToHead : printListFromTailToHead
};

4. 重建二叉树

二叉树:
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根

给定某二叉树的前序遍历和中序遍历,请重建出该二叉树并返回它的头结点。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。

image.png

  • 2.思路: 1.找到根节点,preNode[0] = 1;

前序遍历:1 【2 4 7】【3 5 6 8】 //(根、【左子树】、【右子树】)
中序遍历:【4 7 2】 1 【5 3 8 6】// (【左子树】、根、【右子树】)

2.继续进行递归构建。

前序遍历:2 【4】 【7】//(根、【左子树】、【右子树】)
中序遍历 【4】 2 【7】// (【左子树】、根、【右子树】)

  • 3.方法、 1.创建一个根节点 rootVal = pre[0] //前序的第一个节点即为当前节点
    2.找到根节点在中序遍历的位置
    3.对树进行划分

  • 4.代码

function reConstructBinaryTree(pre, vin)
{
    // pre为前序遍历序列,vin为后序遍历序列
    if(!pre.length || !vin.length){
           return null;
    }
   
    const rootVal = pre[0]; // 找到根节点
    const node = new TreeNode(rootVal);//创建树的根
    
    let i = 0;//根节点在中序遍历结果中的下标;当前左子数的节点个数
    //找到根节点在中序遍历的位置
    for (; i < vin.length; ++i){
        if (vin[i] === rootVal){
            break;
        }
    }
    // 创建树的左右子树
    node.left = reConstructBinaryTree(pre.slice(1, i + 1), vin.slice(0, i)); // 进行左右划分
    node.right = reConstructBinaryTree(pre.slice(i + 1), vin.slice(i + 1)); //
    return node; // 返回树
}
module.exports = {
    reConstructBinaryTree : reConstructBinaryTree
};

5.用两个栈实现队列

用两个栈来实现一个队列,分别完成在队列尾部插入整数(push)和在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。

  • 2.思路

image.png

栈:先入后出,队列:先入先出。只有将栈的元素逐个弹出后,栈底元素才能弹出。

1.采用倒序的方法。进栈

image.png

2.将栈A中的元素,逐个转移到栈B中,倒数输出

image.png

  • 3.方法

1.初始化两个栈列
2.push:入栈到栈中
3.pop:将栈A的元素pop,然后push到栈B中,再返回栈B的pop

  • 4.代码
let stack1 = []
let stack2 = []

function push(node)
{
    // write code here
    stack1.push(node)
}
function pop()
{
    // write code here
    if(stack2.length === 0){
        while (stack1.length > 0){
            stack2.push(stack1.pop())
        }
    }
    return stack2.pop()
}
module.exports = {
    push : push,
    pop : pop
};

6.旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
例如,数组[0,1,2,4,5,6,7]可能变成[4,5,6,7,0,1,2]
数组可能变成[2,2,2,0,2] 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

  • 1.思路 1.数组可以分为两个连续的子数组。【4,5,6,7】、【0,1,2】。
    2.采用二分法进行划分。left→首元素,right→末元素, 中间元素:mid=(left+right)/2mid=(left+right)/2
    3.比较mid和right指向的元素:
    (1).mid<right,【0】。说明mid一定落在了后一个数组【0,1,2】内。缩小范围:right到mid的位置。
    (2).mid>right。说明mid在前一个数组【4,5,6,7】内。缩小范围,left到mid+1 的位置
    (3).mid=right。例如【2,2,2,0,2】的情况。只能顺序查找。缩小范围:right--\

image.png

image.png

4.所找为后一个数组【0,1,2】的第一个元素

  • 2.方法 1.数组大小为0,则返回0
    2.初始化left,right指针,计算mid指针:mid = left + math.floor((left+right)/2)
    3.比较mid和right.
    mid < right, right = mid;
    mid > right, left = mid + 1;
    mid = right, right =right -1 //采用遍历
  • 3.代码
function minNumberInRotateArray(rotateArray)
{
    // write code here
    const len = rotateArray.length
    if (len == 0){
        return 0
    }
    
    // 初始化所有指针
        var left = 0;
        var right = len -1;
        while (left < right){
            var mid = Math.floor((left + right) / 2);
            //对比并缩减范围
            if(rotateArray[mid] < rotateArray[right]){
                right = mid;
            }else if(rotateArray[mid] > rotateArray[right]){
                left = mid + 1;
            }else{
                right--;
            }
            //return rotateArray[left]
        }
        return rotateArray[left];
}
module.exports = {
    minNumberInRotateArray : minNumberInRotateArray
};

10.矩形覆盖

我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2n的大矩形,从同一个方向看总共有多少种不同的方法
比如n=3时,23的矩形块有3种不同的覆盖方法(从同一个方向看):\

image.png

1.思路

image.png image.png image.png

n=3时,n-1的方法,在右边添加两个竖状;n-2的方法,在右边添加1个 所以还是斐波那契

11.二进制中1的个数

输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。

1.用1和二进制数低位进行位与操作,二进制数右移,继续位与

由于负数右移时最高位补1,因此不能采用算术右移,而使用不考虑符号位的逻辑右移

function NumberOf1(n)
{
    // write code here
    var count = 0;
    while(n != 0){
        if((n & 1) == 1){
            count++;
        }
        n = n >>> 1
    }
    return count
}
module.exports = {
    NumberOf1 : NumberOf1
};

2.进行左移,知道左移32位截止

function NumberOf1(n)
{
    // write code here
    if (n === 0){
        return 0
    }
    var count = 0,
        flag = 1;
    while(flag){
        if(n & flag){
            count++;
        }
        flag = flag << 1;
    }
    return count;
}
module.exports = {
    NumberOf1 : NumberOf1
};

3.最高效

n(末1) :【1 0 1 1 0】1
n-1(减1):【1 0 1 1 0】 0
n(末0):【1 0 1】 1 0 0
n-1(减1):【1 0 1】0 1 1

结论:n-1,能将最右边的1→0,且该1右边的所有0→1 n&(n-1):能消除最右边的1

function NumberOf1(n)
{
    // write code here
    var count = 0;
    while (n != 0){
        ++count;
        n = (n-1) & n;
    }
    return count;
}
module.exports = {
    NumberOf1 : NumberOf1
};

4.JavaScript知识tips

1.数字转换成二进制

    var m = 3;
    m.toString(2);

2.移位运算符
>>: 有符号位右移位运算,它把 32 位数字中的所有有效位整体右移,再使用符号位的值填充空位。移动过程中超出的值将被丢弃。

console.log(-1000 >> 8); //返回值为-4

>>>:无符号位右移位运算,对于负数来说,无符号右移将使用 0 来填充所有的空位,同时会把负数作为正数来处理,所得结果会非常大所以,使用无符号右移运算符时要特别小心,避免意外错误。

console.log(-1000 >>> 8); //返回值 16777212

12.数值的整数次方

1.思路

x1=xx^1=x
x2=xxx^2=x*x
x4=(x2)2x^4=(x^2)^2
x8=(x4)2x^8=(x^4)^2

2.方法

1.flag用来标志幂数是否为倒数,res暂时存储每一步的结果 2.判断幂数=0,<0,>0的情况,若小于0 ,则取反 3.>0,低位开始和1相与,res和已改变的幂相乘 4.改变底,幂右移 5.根绝flag判断返回res还是1/res

3.代码

//方法一
return Math.pow(base, exponent);
// 方法二
function Power(base, exponent)
{
    // write code here==
    var isGzero = true; // 对指数进行判断,返回的结果是res还是res的倒数
    var res = 1;
    if(exponent == 0){
        return 1;
    }
    if(exponent < 0){
        exponent = -exponent;
        isGzero = false;
    }
    while(exponent > 0){
        if(exponent&1 == 1){
            res *= base;
        }
        base *= base;
        exponent >>= 1;
    }
    return isGzero ? res:(1/res);
}
module.exports = {
    Power : Power
};
 

13.调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

1.思路

// 双指针法, 左指针往右遇偶停下,右指针往左遇奇停下。
都停下时,左右指针的内容互换位置。
直至两指针相遇

//双数组法 奇数加入奇数组,偶数加入偶数组 最后合并

2.方法

3.代码

//双指针法
function reOrderArray( array ) {
    // write code here
    var left = 0;
    var right = array.length - 1;
    while(left < right){
        while(left < right&& array[left]%2 == 1){
            left++;
        }
        while(left < right && array[right]%2 == 0){
            right--;
        }
        [array[left],array[right]] = [array[right], array[left]];
        left++;
        right--;
    }
    return array;
}
module.exports = {
    reOrderArray : reOrderArray
};

//双数组法
function reOrderArray( array ) {
    // write code here
    var arr1 = [];
    var arr2 = [];
    for (let i of array){//ES6的for of循环
        if(i%2 == 0){
            arr2.push(i);
        }else{
            arr1.push(i);
        }
    }
    return [...arr1,...arr2]
}
module.exports = {
    reOrderArray : reOrderArray
};

14.链表中最后个K结点

输入一个链表,输出一个链表,该输出链表包含原链表中从倒数第k个结点至尾节点的全部节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。

1.思路

Null处为fast指针,所找点为slow指针。
先让fast从起点处右移k步,然后slow和fast一起向右移动。
fast到达NULL时,slow正好在倒数第K个结点上。

2.方法

1.判断头结点为空或k<0;
2.初始化slow,fast指针
3.fast先走(循环到<k-1),若fast的下一个结点存在,就向下走,若不存在,则停下(return null) 4.若fast下一个结点存在,fast和slow一起向下走。

3.代码

function FindKthToTail( pHead ,  k ) {
    // write code here
    if (pHead == null || k<=0 ){////如果头节点为空或k的值小于0,就直接返回null
        return null;
    }
    var fast = pHead;
    var slow = pHead;
    for(var i = 0; i< k-1; i++){//让第一个指针走k-1步
        if(fast.next){//如果下一个节点存在就继续往下走,否则k小于链表长度,直接返回null
            fast = fast.next;
        }else{
            return null
        }
    }
    while(fast.next){
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}
module.exports = {
    FindKthToTail : FindKthToTail
};

15.反转链表

输入一个链表,反转链表后,输出新链表的表头。

1.思路

image.png 链表的指针为单向,所以不能查找目前结点的前一个结点
从头开始,用pre保存上一项,cur当前项,tmp下一项。
pre和cur一组,不停移动,知道cur为null,则pre指向的是新的结点。

pre结点可以用来反转方向,为了避免反转之后链表断开,用temp结点暂时保存next结点

2.方法

1.初始化定义各结点,pre = null,cur = head,temp = null
2.只要cur没有走到最后的null,存储next结点,断开(next= pre),pre,cur向末移动
3.返回pre

3.代码

function ReverseList(pHead)
{
    // write code here
    let pre = null; //前结点
    let cur = pHead; //当前结点
    let temp = null; //初始化的下一个结点
    
    while(cur !== null){
        temp = cur.next;
        //断开到下一个结点的指针,然后pre、cur进行同时移动
        cur.next = pre; //pre和next是否同时为null,不是则断开next的(赋值为pre)
        pre = cur; // pre移动
        cur = temp; //temp移动
    }
    return pre
}
module.exports = {
    ReverseList : ReverseList
};

16.合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

1.思路

image.png

2.方法

1.初始化一个新的链表(内容为空)
2.判断两连边是否有空
3.比较两者的值,head→小的那个,小的指针向下移动。head向下移动。 4.判断谁先到头,则将另一个剩下的全部贴给head。

3.代码

function ListNode(x){
    this.val = x;
    this.next = null;
}
function Merge(pHead1, pHead2)
{
    var head = new ListNode(null)//初始化一个节点值为0的空节点
    let dummy = head; //存储新链表的当前结点
    
    if(pHead1 == null) return pHead2;
    if(pHead2 == null) return pHead1;
    if(pHead1 == null && pHead2 == null) return null;
    
    while(pHead1 && pHead2){
        if(pHead1.val < pHead2.val){
            head.next = pHead1
            pHead1 = pHead1.next
        }else{
            head.next = pHead2
            pHead2 = pHead2.next
        }
        head = head.next
    }
    if(!pHead1){
        head.next = pHead2; //pHead1空了的话
    }else if(!pHead2){
        head.next = pHead1
    }
    return dummy.next
}
module.exports = {
    Merge : Merge
};

17.树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)\

image.png image.png

1.思路

1.【是否含有子结构】
(1).根节点重合:
(2).【子树含有子结构】。不考虑根节点,判断A的左子树/右子树是否含有B的子结构。 2.【根结点对应,树是否一样】
(0).判断值前先判断是否有空 (1).根节点的值是否相同
(2).左右子树是否可以对应

2.方法

1.判断【结构】,判断A、B根节点的【值】,A子树是否是否含有B【结构】。 2.判断【值】,值相同,继续判断左右子树的【值】

3.代码

// 判断结构
function HasSubtree(pRoot1, pRoot2)
{
    // write code here
    if(pRoot1 == null || pRoot2 == null){
        return false;
    }
    return isSubTree(pRoot1, pRoot2) ||
        HasSubtree(pRoot1.left, pRoot2) || //A的左子树是否含有B
        HasSubtree(pRoot1.right, pRoot2) //A的右子树是否含有B
}
//判断值
//1.判断是否为空
//2.判断根节点的值是否相同
//3.判断左右子树是否对应
function isSubTree(root1, root2){
    if(root2 == null) return true;
    if(root1 == null) return false;
    if(root1.val == root2.val){
        return isSubTree(root1.left, root2.left) && isSubTree(root1.right, root2.right);
    }else{
        return false;
    }
}
module.exports = {
    HasSubtree : HasSubtree
};

18.二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像。

1.思路

image.png 交换左右子树,讲左右子树分别求镜像

2.方法

1.判断根节点是否为空
2.用临时变量存储左子树
3.对左子树进行镜像,对右子树进行镜像
4.返回原由根节点

3.代码

function Mirror( pRoot ) {
    // write code here
    if(!pRoot) return null;
    let tmp = pRoot.left; //暂存左子树。
    pRoot.left = Mirror(pRoot.right);
    pRoot.right = Mirror(tmp);
    return pRoot;
    
}
module.exports = {
    Mirror : Mirror
};

19.顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字

1.思路

image.png

2.方法

3.代码

function printMatrix(matrix)
{
    // write code here
    if(matrix == null || matrix.length == null) return;
    
    var top = 0;
    var bottom = matrix.length - 1;
    var left = 0;
    var right = matrix[0].length - 1;
    let result = [];//初始化输出数组
    
    while(true){
        //1.从左向右打印
        for(var i = left; i<=right; i++){
            result.push(matrix[top][i]);
          }
        top++;
        if (top > bottom ) break;
        
        //2.从上到下
        for(var i=top; i<=bottom; i++){
            result.push(matrix[i][right])
        }
        right--;
        if(right <left) break;
        
        //3.从右到左
        for(var i=right; i>=left; i--){
            result.push(matrix[bottom][i])
        }
        bottom--;
        if(bottom < top) break;
        
        //4.从下往上
        for(var i=bottom; i>=top;i--){
            result.push(matrix[i][left]);
        }
        left++;
        if(left > right ) break
    }
    return result;
}
module.exports = {
    printMatrix : printMatrix
};

20.包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数,并且调用 min函数、push函数 及 pop函数 的时间复杂度都是 O(1)
push(value):将value压入栈中
pop():弹出栈顶元素
top():获取栈顶元素
min():获取栈中最小元素

1.思路

image.png
【数据栈S1】、【辅助栈S2】。
S2栈顶元素即为当前S1中所有元素的最小值 min:返回,S2.top()
push:压入S1中,比较和S2.top()
pop:S1、S2中栈顶元素弹出

2.方法

3.代码

var dataStack = [];
var minStack = [];

function push(node)
{
    if(minStack.length === 0 && dataStack.length === 0){
        dataStack.push(node);
        minStack.push(node);
    } else{
        dataStack.push(node);
        var stack2Top = minStack[minStack.length-1];
        if(node < stack2Top)
            minStack.push(node)
        else
            minStack.push(stack2Top);
    }
}
function pop()
{
    minStack.pop();
    return dataStack.pop();
}
function top()
{
    return dataStack[dataStack.length-1];
}
function min()
{
    return minStack[minStack.length-1];
}

module.exports = {
    push: push,
    pop: pop,
    top: top,
    min: min
}

22.从上往下打印二叉树

从上往下打印出二叉树的每个节点,同层节点从左至右打印\

image.png
image.png

1.思路

image.png

2.方法

1.定义队列和返回数组,并将根节点加入到队列中
2.如果队列不为空,则打印首元素,队列头部(.shift)弹出值加入到result中
3.判断队列是否有左右子树。

3.代码

function PrintFromTopToBottom(root)
{
    // write code here
    var queue = [];
    var result = [];
    queue.push(root); // 根节点加入到队列中
    
    if(root == null) return result;
    
    while(queue.length){
        var temp = queue.shift();
        result.push(temp.val); // 将弹出的值加入到result中
        if(temp.left){
            queue.push(temp.left)
        }
        if(temp.right){
            queue.push(temp.right)
        }
    }
    
    return result;
}
module.exports = {
    PrintFromTopToBottom : PrintFromTopToBottom
};

23.二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。(ps:我们约定空树不是二叉搜索树)

1.思路

二叉搜索树:
若它的左子树不空,则左子树上所有结点的值【均小于】它的根节点的值; 若它的右子树不空,则右子树上所有结点的值【均大于】它的根结点的值; 它的左、右子树也分别为【二叉搜索树】。
image.png
后序遍历:左、右、根
image.png
输入:1 3 2 6 5,
【1 3 2】【6】【5】(左、右、根)
A0171AD56A969EE979CF7A6E5CE573E0.png
若最后P=j,true;

2.方法

1.递归结束条件,start>= end,定义移动的index 2.确保[start, right -1]所有的数都比根节点小的,就是左子树
3.检查[right, end]的值是否都比根节点大 4.index == end,说明是二叉搜索树。

3.代码

function VerifySquenceOfBST(sequence)
{
    // write code here
    function judge(arr, start, end){
        // 递归结束的条件:当前数组的元素最多只有一个
        if(start >= end){ 
            return true
        }
        
        // 确保[start, right -1]所有的数都比根节点的值小的,就是左子树
        var index = start;
        while(arr[index] < arr[end]){
            index++;
        }
        // 检查剩余元素是否都比根节点的值大
        var right = index;
        while(arr[index] > arr[end]){
            index++;
        }
       
        return index==end && judge(arr, start,right-1)&& judge(arr,right, end-1);
    }
    if(sequence.length == 0){
        return false
    }
    return judge(sequence, 0, sequence.length -1)
}
module.exports = {
    VerifySquenceOfBST : VerifySquenceOfBST
};

24.二叉树中和为某一值的路径

输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

1.思路

1.从根节点开始,所以用前序深度优先搜索。

image.png

2.方法

1.从根节点开始,记录路径;target - 根节点的值 2.target减到了0,且节点为叶子节点,说明路径合法,将路径的值拷贝到res中 3.递归判断左右子树 4.弹出最后一项不符合要求的

3.代码

function FindPath(root, expectNumber)
{
    // write code here
    var res = []; // 存放最终的答案,
    var path = []; // 记录遍历的路径
    function findPath(root, target){
        if (root == null){
            return 
        }
        path.push(root.val)
        target -= root.val
        if (target == 0 && root.left == null && root.right == null){
            // target减到了0且节点为叶子节点,说明找到了合法的路径
            // 防止改变原始path,所以用slice进行提取
            res.push(path.slice()) // 将路径加入到答案中
        }else{
            if(root.left) findPath(root.left, target)
            if(root.right) findPath(root.right, target)
        }
        // 弹出最后一项不符合要求的元素
        path.pop()
    }
    findPath(root, expectNumber)
    return res
}
module.exports = {
    FindPath : FindPath
};

25.复制复杂链表

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。

1.思路

image.png
image.png 1.复制节点的值,并且放到原节点的后面
2.

2.方法

3.代码

function RandomListNode(x){
    this.label = x;
    this.next = null;
    this.random = null;
}

function Clone(pHead)
{
    if(pHead === null) return;
    // write code here
    // 1.复制节点的值,并且放到原节点的后面
    var cur = pHead;
    while(cur){
        var curCopy = new RandomListNode(cur.label) //复制每一个节点
        //并将节点插入到后面 【cur】【curCopy】,两个next指针先平移
        curCopy.next = cur.next
        cur.next = curCopy
        cur = curCopy.next 
    }
    
    // 2.构造random,从头结点开始
    cur = pHead
    while(cur){
        if(cur.random) {
            cur.next.random = cur.random.next
        }
        cur = cur.next.next //cur, curCopy,cur.(next)原本的
    }
    
    //3.拆分链表,从头结点开始
    cur = pHead
    var newHead =pHead.next // 记录返回的复杂链表的头结点
    while(cur.next){
        var temp = cur.next
        cur.next = temp.next //间隔相连
        cur = temp
    }
    return newHead
}
module.exports = {
    Clone : Clone
};

26.二叉搜索树与双向链表

image.png

1.思路

需要排序,所以用中序遍历:左根右。遍历到根节点时,左子树已经成为双向链表。 2.根节点加入到末尾,根的pre指向6,6的next指向10 3.指向右边根节点。

  1. 递归思想:把大问题转换为若干小问题;
  2. 由于JavaScript中并没有链表或者Tree这样的原生数据结构,都是通过对象模拟的,因此最终要返回的是指向双向链表首结点的指针;
  3. 将左子树构成双向链表,返回的是左子树的尾结点,将其连接到root的左边;
  4. 将右子树构成双向链表,将其追加到root结点之后,并返回尾结点;
  5. 向左遍历返回的链表至头结点处,即为所求双向链表的首结点

2.方法

3.代码

function Convert(pRootOfTree) {
    if (!pRootOfTree) {
        return null;
    }
    var head = null;
    var tail = null; //用来标记转换好的双向链表头部和尾部
    tail = ConvertNode(pRootOfTree, tail);
//     head.left = tail;
    head = tail;
    while (head && head.left) {
        head = head.left
    }
    return head;
}

function ConvertNode(cur, tail) {
    if (!cur) {
        return;
    }
    if (cur.left) {
        tail = ConvertNode(cur.left, tail);
    }
    cur.left = tail;
    if (tail) {
        tail.right = cur;
    }
    tail = cur;
    if (cur.right) {
        tail = ConvertNode(cur.right, tail);
    }
    return tail;
}

module.exports = {
    Convert : Convert
};

27.字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

1.思路

image.png

1.先固定第一个字符,然后求后面字符的排列
2.递归思想,后面字符固定第一个,然后求再之后的排列
3.用set记录再次位置上已经排列过的字符

2.方法

3.代码

52.两个链表的第一个公共节点

输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

数据范围: n \le 1000n≤1000 要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

  • 1.思路

image.png

image.png

  • 2.方法 1.定义两个头结点 2.只要头结点不相等,就按照特定轨迹移动 结点不为空,就向后一个结点移动,否则就到另一个链表 3.跳出循环,找到了公共节点,pA pB都是空的,所以直接返回pA
  • 3.代码
function FindFirstCommonNode(pHead1, pHead2)
{
    // write code here
    // a1 → a2 → a3↘
    //              c1 → c2 → c3
    // b1 →b2 →b3↗
    let pA = pHead1, pB = pHead2 // 定义两个头结点
    while (pA !== pB) { // 只要两个头结点不相等, 就按照特定的轨迹移动
        pA = pA ? pA.next : pHead2 // pA不为空,就移动端到pA的后续节点,否则移动到B链表的头结点
        pB = pB ? pB.next : pHead1 //
    }
    return pA
}
module.exports = {
    FindFirstCommonNode : FindFirstCommonNode
};