32.把数组排成最小的数
【2022.4.26:简单偏难】
- 按字符串编码的规则比较每个数组元素的大小,若mn<nm,则m在n前。以3,30排序为例,303<330(字符串编码),则排列时,30在3的前面。
- 数组的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++;
}
}