西瓜仔刷题集3--剑指offer(31-40题)

32.把数组排成最小的数
【2022.4.26:简单偏难】

  1. 按字符串编码的规则比较每个数组元素的大小,若mn<nm,则m在n前。以3,30排序为例,303<330(字符串编码),则排列时,30在3的前面。
  2. 数组的sort方法,默认对每个元素按字符串编码的规则比较大小。在这里对字符串不能单纯比较两个元素的字符串编码大小,需要定义一个新的排序函数:拼接之后再比较。
    举例说明:[10, 2],设a = 10,b = 2,则a+b = “102”,b+a=“210”,要拼接成最小的数,则:
    如果a + b < b + a,则ba拼接比ab拼接大,a应该在b的左边
    如果a + b < b + a,则ab拼接比ba拼接大,a应该在b的右边
    function PrintMinNumber(numbers)
    {
    for (let i = 0; i < numbers.length;i++) {
    numbers[i] = String(numbers[i])
    }
    numbers.sort((num1,num2) => (num1 + num2) - (num2 + num1));
    //join函数连接每一项
    return numbers.join(‘’)
    }

33.求出第n个丑数
【2022.4.27:中等偏难】

重在理解思路,详见博客

function GetUglyNumberSolution(index) {
  if (index < 7) return index;
  const res = [];
  res[0] = 1;
  let t2 = 0,
    t3 = 0,
    t5 = 0;
  for (let i = 1; i < index; i++) {
    res[i] = Math.min(res[t2] * 2, res[t3] * 3, res[t5] * 5);
    if (res[i] === res[t2] * 2) t2++;
    if (res[i] === res[t3] * 3) t3++;
    if (res[i] === res[t5] * 5) t5++;
  }
  return res[index - 1];
}

34.第一个只出现一次的字符
【2022.4.27:简单】

解题思路:
(1)用map记录字符出现的次数:如果map的值为空,那么就置为1;如果不为空,就++1就行;
(2)最后从头开始遍历map,判断为1直接返回下标

function FirstNotRepeatingChar(str)
{
    if(str.length<1||str.length>10000) return -1;
    const map={};
    for(let i=0;i<str.length;i++){
        let temp=str[i];
        if(!map[temp])
            map[temp]=1;
        else 
            map[temp]++;    
    }
    
     for(let i=0;i<str.length;i++)
         {
        if(map[str [i] ]===1)
           return i;
         }
        
        return -1;
      
}

35.归并排序求解数组中中的逆序对
【2022.5.6:难】
**
**核心思想:**函数快速统计逆序对的大体过程如下:
1.递归调用,拿到左子数组和右子数组的逆序对(此时,左子数组和右子数组也都排序完成了)
2.指针 p 和 q分别指向左子数组和右子数组的最右侧,此时会有 2 种情况:
arr[p] > arr[q]:
那么说明arr[p]大于右子数组中所有元素,逆序对增加j - start - length,向左边移动指针 p
arr[p] <= arr[q]:
对arr[p]来说,不存在逆序对,向左边移动指针 j
i 和 j 遍历完各自数组后,最后返回逆序对之和即可
3.思考为什么:在求left和right时,要把data作为copy传入,copy作为data传入(这里不明白)

function InversePairs(data) {
  if (!data || data.length < 2) return 0;
  const copy = data.slice();
  let count = 0;
  count = mergeCount(data, copy, 0, data.length - 1);
  return count % 1000000007;
}
function mergeCount(data, copy, start, end) {
  if (start === end) return 0;
  const mid = end - start >> 1,
    left = mergeCount(copy, data, start, start + mid), // 注意参数,copy作为data传入
    right = mergeCount(copy, data, start + mid + 1, end); // 注意参数,copy作为data传入
  let [p, q, count, copyIndex] = [start + mid, end, 0, end];

  while (p >= start && q >= start + mid + 1) {
  //if用来计算逆序个数
  //p是左边数组的结束位置,q是右边数组的结束位置
    if (data[p] > data[q]) {
    //data[p] > data[q],按照从大到小排序,把较大值赋给copy数组,
    //赋值完成后,copyIndex减一,p下标减一
      copy[copyIndex--] = data[p--];
      count = count + q - start - mid;
    } else {
      copy[copyIndex--] = data[q--];
    }
  }
    //while控制归并排序的完成
  while (p >= start) {
  //跳出上面while循环后,p还没遍历完,就把p都加入剩余数组
    copy[copyIndex--] = data[p--];
  }
  while (q >= start + mid + 1) {
    //跳出上面while循环后,q还没遍历完,就把q都加入剩余数组

    copy[copyIndex--] = data[q--];
  }
  return count + left + right;
}```

(36)两个链表中的第一个公共节点
【2022.5.9 中等】
解题思路:
1.公共节点,并不是两个节点的值相同就是公共节点。
而是在第一链表和第二链表中都存在一个节点,该节点往后的子链表在两个链表中是相同的。(注意,题意在这里指的是尾部)
在这里插入图片描述

2.如果我们从两个链表的尾部开始往前比较,那么最后一个相同的节点就是我们要找的节点,因此,我们需要链表倒序压入栈中,比较两个栈顶节点是否相同,如果相同,将栈顶弹出比较下一个栈顶,直到找到最后一个相同的栈顶。时间复杂度O(m + n)。

function FindFirstCommonNode(pHead1, pHead2)
{
    //把链表压入栈中
    if (!pHead1 || !pHead2) {
        return null
    }
    
    let arr1=[];
    let arr2=[];
    while(pHead1){
        arr1.push(pHead1);
        pHead1=pHead1.next;  
    }
    
    while(pHead2){
        arr2.push(pHead2);
        pHead2=pHead2.next;  
    }   
    
    //从栈顶开始遍历,
    let same=null;
    let i=arr1.length-1;
    let j=arr2.length-1;
   
    while(i>=0 && j>=0){
    if(arr1[i]==arr2[j]){
         same=arr1[i];
    }
        i--;
        j--;
    }
    
    return same;
    
}

37.数字在排序数组中出现的次数
【22.5.11 中等】

function GetNumberOfK(data, k)
{
    if(GetStart(data, k)===-1 && GetEnd(data, k)===-1) return 0;
     return GetEnd(data,k)-GetStart(data, k)+1;
    // write code here
}

function GetStart(data, k)
{
    let left=0,right=data.length-1;
    let  mid = left + right >> 1;
    while(left<=right){
    if(data[mid]>k)
    {right=mid-1;} 
    else if(data[mid]<k)
    {left=mid+1;}
    else if(data[mid-1]===k && mid-1>=0)
        //表示中间值等于目标值,但此时有相等的情况,前一个数值也等于key,
        //因此右边界左移,因为start存在前面的位置
    {right=mid-1;} 
    else return mid;
    mid = left + right >> 1;
    }
    return -1;
    // write code here  
}

function GetEnd(data, k)
{
    let left=0,right=data.length-1;
    let mid = left + right >> 1;

    while(left<=right){
    if(data[mid]>k)
       {right=mid-1;}
    else if(data[mid]<k)
    { left=mid+1; }
    else if(data[mid+1]===k && mid+1>=left)
        //表示中间值等于目标值,但此时有相等的情况,后一个数值也等于key,
        //因此左边界左移,因为end存在后面的位置
    {left=mid+1;} 
    else return mid;
    mid = left + right >> 1;
    }
 
    return -1;
   // write code here
}

思考: 二分法求数组中间数值时,为什么最好用

int mid = left + (right - left) /2;

来代替

int mid = (right + left) / 2;

改写法相当于:如果数组⻓度为偶数,中间位置有两个元素,取靠左边的。

原因:
Integer都有自己的表示范围,他有定义MAX_VALUE和MIN_VALUE;
left <= MAX_VALUE和 right <= MAX_VALUE是肯定的
但是left+right <= MAX_INT 我们无法确定,所以会造成栈溢出

38. 二叉树的深度
【2022.5.12 简单】

递归实现深度遍历

function TreeDepth(pRoot)
{
    if(pRoot==null) return 0;
    let RightDep=TreeDepth(pRoot.right);
    let LeftDep=TreeDepth(pRoot.left);
    return Math.max(RightDep,LeftDep)+1;

}

39. 判断平衡二叉树
【2022.5.12 简单】

两种方法:
第一种方法:
正常思路,应该会获得节点的左子树和右子树的高度,然后比较高度差是否小于1。问题就是节点重复遍历了,影响效率了。
第二种方法:
改进办法就是在求高度的同时判断是否平衡,如果不平衡就返回-1,否则返回树的高度。并且当左子树高度为-1时,就没必要去求右子树的高度了,可以直接一路返回到最上层了

//第一种方法
function IsBalanced_Solution(pRoot) {
  if (pRoot == null) return true;
  let leftLen = TreeDepth(pRoot.left);
  let rightLen = TreeDepth(pRoot.right);
  return Math.abs(rightLen - leftLen) <= 1 && IsBalanced_Solution(pRoot.left) && IsBalanced_Solution(pRoot.right);
}
function TreeDepth(pRoot) {
  if (pRoot == null) return 0;
  let leftLen = TreeDepth(pRoot.left);
  let rightLen = TreeDepth(pRoot.right);
  return Math.max(leftLen, rightLen) + 1;
}
/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */

//第二种方法
function IsBalanced_Solution(pRoot)
{
    // write code here
    return TreeDepth(pRoot) !==-1;
}
function TreeDepth(pRoot){
    if(pRoot === null) return 0;
    var left = TreeDepth(pRoot.left);
    if(left === -1)return -1;
    var right = TreeDepth(pRoot.right);
    if(right === -1)return -1;
//这里是返回长度
    return Math.abs(left - right) > 1 ? -1: Math.max(left,right) + 1;
}

40.数组中只出现一次的数字
【2022.5.14 简单】**

function FindNumsAppearOnce( array ) {
    // write code here
    const res=[];
    for(let i=0;i<array.length;i++){
      if(array.indexOf(array[i]) === array.lastIndexOf(array[i])){
        res.push(array[i]);
        }
    }
    return res.sort();
}

31.输出该数32位二进制表示中1的个数
【2022.4.25:简单】

function NumberOf1(n)
{
    let count=0,flag=1;
    while(flag){
    if(flag&n)
        count++;
        flag=flag << 1;
    }
    return count;
}
补充笔记:判断1的个数
(在这里注意JS移位运算符(<<>>>>>--见常用笔记)
// 挪动1的位置
console.log(n.toString(2));
let count = 0;
for (let i = 0; i < 32; i++) {
	if ((n & (1 << i)) === (1 << i)) {
		count++;
	}
}
console.log(count);
// 挪动本身
count = 0;
for (let i = 0; i < 32; i++) {
	if ((n >>> i) & 1) {
		count++;
	}
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值