JS算法(三)

本文介绍了9个使用JavaScript实现的算法问题,包括找到数组中间位置、用栈实现队列、括号的有效性检查、合并升序链表、生成有效括号、最长回文子串、查找首次出现的字符、二分查找、目标值在排序数组中的位置以及查找目标值的开始和结束位置。每个问题都包含了思路和JS代码实现。

1.找到数组的中间位置

给你一个下标从 0 开始的整数数组 nums ,请你找到 最左边 的中间位置 middleIndex
(也就是所有可能中间位置下标最小的一个)。

中间位置 middleIndex 是满足 nums[0] + nums[1] + … + nums[middleIndex-1] ==
nums[middleIndex+1] + nums[middleIndex+2] + … + nums[nums.length-1]
的数组下标。

如果 middleIndex == 0 ,左边部分的和定义为 0 。类似的,如果 middleIndex == nums.length -
1 ,右边部分的和定义为 0 。

请你返回满足上述条件 最左边 的 middleIndex ,如果不存在这样的中间位置,请你返回 -1 。

leetcode 出处和题解,我觉得这个题解非常好,就直接用了。
https://leetcode-cn.com/problems/find-the-middle-index-in-array/solution/zhao-dao-shu-zu-de-zhong-jian-wei-zhi-by-s8cy/

1.思想

在这里插入图片描述

2.JS代码

var findMiddleIndex = function(nums) {
    let total = nums.reduce(a, b =>  a+b ,0)
    let sum = 0;
    for (i = 0; i < nums.length; i++) {
        if (total == 2 * sum + nums[i])
        {
            return i
        } 
        sum += nums[i] 
    }
    return -1
};

2.用两个栈实现一个队列。

队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

2.1 思想

1 )掌握栈的基本概念, 栈:先进后出;队列:先进先出。
2 )掌握JS数组处理方法
unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度

2.2 JS代码

// 栈:先进后出;队列:先进先出
//队列 unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。

var CQueue = function() {
    this.inStack = [];
    this.outStack = [];
};
// push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度
CQueue.prototype.appendTail = function(value) {
    
    this.inStack.push(value);
};

CQueue.prototype.deleteHead = function() {
    if (!this.outStack.length) {
        if (!this.inStack.length) {
            return -1;
        }
        this.in2out();
    }
    return this.outStack.pop();
};

CQueue.prototype.in2out = function() {
    while (this.inStack.length) {
        this.outStack.push(this.inStack.pop());
    }
};

3.给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例 1: 输入:s = “()” 输出:true

示例 2: 输入:s = “()[]{}” 输出:true

示例 3: 输入:s = “(]” 输出:false

3.1 思想

到有括号的题,第一反应就要是栈
  1. 先为所有类型的括号做匹配,如果出现’(’ , 就把一个 “)”放入栈。
  2. 如果栈的长度为0 则说明没有括号,返回false
  3. 括号都放进栈之后,把输入的值和栈进行匹配,不相等则返回false。

3.2 JS代码

var isValid = function(s) {

const stack = [];
 

 for (let val of s){
     if(val === '('){
         stack.push(')')
     }
     else if(val === '[')
     {
         stack.push(']')
     }
     else if(val === '{')
     {
         stack.push('}')
     }
     else if(stack.length === 0 ||    val !== stack.pop() ){  return false   }
 }

 return stack.length === 0 ;

};

4.将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

4.1 思想

递推的方式

  1. 定义一个空链表用来存储最后的结果。
  2. 先比较两个链表的第一个节点,把小的先存到空链表中,依次比较。
  3. 两个链表长度不一,当有一个链表为空时,直接将后面的数值存到存储列表中。
  4. 将拼接后的链表返回。

4.2 JS代码

var mergeTwoLists = function(list1, list2) {
 // 空链表
const l6 = new ListNode();
let current = l6;

while(list1 && list2) {
// 将current.next 指向最小的一个链表
    if(list1.val < list2.val ){
        current.next = list1;
        list1 = list1.next

    }
    else{
        current.next = list2;
        list2 = list2.next;
    }
// 当前的链表向后位移
    current = current.next;
}
 // 处理剩下的
    if(list1 === null){
        current.next = list2;
    }
    if(list2 === null){
       current.next = list1;
    }

return l6.next;
};

5.数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3 输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
示例 2:

输入:n = 1 输出:[“()”]

5.1 思想

根据题目,想到了深度优先搜索(DFS),首先确定边界,
当 字符串长度为 2*n的时候,括号的数量为偶数,即匹配成功。就可以把匹配的的字符串返回。

尝试可能的状态,以左括号“( ” 判断是否有剩余,如果有则继续进行选择。如果左括号比右括号剩的多才能选右括号。这样一遍遍的查找下去,直到满足边界条件。

DFS算法:

function dfs(step)
{
	if(边界成立)
	{
		走到最深处
       ......
		return;
	}
	for(尝试每一种可能的状态)
	{
		if(如果这种状态可行){  //剪枝
            把这种可能的状态标记,表示走过 
            继续下一步dfs(step+1)   //状态转移
            把这种标记去除  //回溯
		}
	}
}

5.2 JS代码

var generateParenthesis = function(n) {
       const res = [];

       const dfs = (lRemain, rRemain, str) =>{
           if(str.length === 2 * n){
               res.push(str)
               return;
           }

           if(lRemain > 0){
               dfs(lRemain-1, rRemain, str + '(')
           }
            // 右括号比左括号剩的多,才能选右括号
           if(lRemain < rRemain){
               dfs(lRemain, rRemain-1, str + ')')
           }
       };

       dfs(n, n, '')
        return res;
};

6.给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串 。

在构造过程中,请注意 区分大小写 。比如 “Aa” 不能当做一个回文字符串。

示例 1:

输入:s = “abccccdd” 输出:7 解释: 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
示例 2:

输入:s = “a” 输入:1
示例 3:

输入:s = “bb” 输入: 2

6.1 思想

利用JS的中map方法,存储字符串分割后的数组。
遍历数组中的索引,用map的has 方法判断数组中是否有重复的字符。如果有删除此字符,计数加2.如果没有,就把这个字符放到map中。
最后,如果计数值小于字符串的长度,那么说明至少有一个字符串单个出现。而在回文字符串中,单个出现的字符只能有一个,所以利用长度对比来判断,count 是否应该+1.

6.2 JS代码

var longestPalindrome = function(s) {
   let map = new Map();
   let count = 0;

   let arr = s.split('');
   for (let val of arr){
       if(map.has(val)){
           map.delete(val);
           count += 2;
       }
       else{
           map.set(val)
       }
   }
  return count < arr .length ? count + 1  : count;

};

7.在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

示例 1:

输入:s = “abaccdeff” 输出:‘b’ 示例 2:

输入:s = “” 输出:’ ’

7.1 思想

巧用JS的indexOf() 、 lastIndexOf() 判断字符串是否有重复的值出现。有就返回。没有就退出循环,返回‘ ’ .

7.2 JS代码

var firstUniqChar = function(s) {
for (let i of s){
    if(s.indexOf(i)===s.lastIndexOf(i)) return i;
}
return ' '

};

8.给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5 输出: 2
示例 2:

输入: nums = [1,3,5,6], target = 2 输出: 1
示例 3:

输入: nums = [1,3,5,6], target = 7 输出: 4

8.1 思想

二分法的思想,其中二分法有两种形式:
①左闭右开[ left , right )
当左闭右开时, left = right 无意义,所以条件是while(left < right)
查找的区间确定了值的范围,如果值不在范围内,就返回left,因为是左闭右开的,以为基准 left开始查找的。

while(left < right)的终止条件是left == right,写成区间的形式就是[left, right],或者带个具体的数字进去[2, 2],这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间[2, 2]被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。

②左闭右闭 [ left , right ]
左闭右闭, left = right 有意义,所以条件是while(left <= right) ,判断值是否在查找区间里,如果在就返回。如果不在,就以right为基准判断是小于范围内的值还是大于范围内的值,返回的是right + 1是处理插入值的四种情况:

     目标值在数组所有元素之前  [0, -1] 
     目标值等于数组中某一个元素  return middle;
     目标值插入数组中的位置 [left, right],return  right + 1
     目标值在数组所有元素之后的情况 [left, right], return right + 1

while(left <= right)的终止条件是left == right + 1,写成区间的形式就是[right + 1, right],或者带个具体的数字进去[3, 2],可见这时候区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。

8.2 JS代码

左闭右开的二分法:
找到 target 的最左侧索引,要收紧右侧边界以锁定左侧边界。

var searchInsert = function(nums, target) {
    let right = nums.length ;
    let left = 0;
    while(left < right){
        let middle = left +((right - left) >> 1);
        if(nums[middle] > target){
            right = middle;
        } else if (nums[middle] < target) {
            left = middle + 1;
        } else { return middle; }
    }
    // 收左边界
    return left;
};

左闭右闭的二分法:

var searchInsert = function(nums, target) {
    let right = nums.length - 1;
    let left = 0;
    while(left <= right){
        let middle = left +((right - left) >> 1);
        if(nums[middle] > target){
            right = middle - 1;
        } else if (nums[middle] < target) {
            left = middle + 1;
        } else { return middle; }
    }
    return right + 1;
};

9. 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4 解释: 9 出现在 nums 中并且下标为 4
示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2 输出: -1 解释: 2 不存在 nums 中因此返回 -1

9.1 思想

二分法的要点:① 判断执行条件,②判断区间取值范围, ③判断可能取值的结果

9.2 JS代码

①左闭右开:

var search = function(nums, target) {
    let left = 0;
    let right = nums.length;
    // ① 判断执行条件
    while (left < right){
        let middle = left + ((right - left) >> 1);
        // ②判断区间取值范围
        if (nums[middle] > target) {
            right = middle;
        } else if (nums[middle] < target) {
            left = middle + 1;
        } else {
            return middle;
        }
    }
    // ③判断可能取值的结果
    return -1;
};

②左闭右闭

var search = function(nums, target) {
    let left = 0;
    let right = nums.length - 1;
      // ① 判断执行条件
    while (left <= right){
        let middle = left + ((right - left) >> 1);
        // ②判断区间取值范围
        if (nums[middle] > target) {
            right = middle - 1;
        } else if (nums[middle] < target) {
            left = middle + 1;
        } else {
            return middle;
        }
    }
    // ③判断可能取值的结果
    return -1;
};

10.在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4] 示例 2:

输入:nums = [5,7,7,8,8,10], target = 6 输出:[-1,-1] 示例 3:

输入:nums = [], target = 0 输出:[-1,-1]

10.1 思想

二分法,经过了上面两题,我们可以大概知道
①左闭右闭:若初始化 right = nums.length - 1,则「搜索区间」是 [left, right]
则 while (left <= right),且 left = mid+1 和 right = mid-1 。
只需找到一个 target 的索引,当 nums[mid] == target 时可以立即返回 return middle;

②左闭右开:若初始化 right = nums.length,则「搜索区间」是 [left, right),则 while (left < right),且 left = mid + 1 和 right = mid

10.2 JS代码

var searchRange = function(nums, target) {
    const getRightBorder = function(nums, target) {
        let rightBorder = -2;
        let left = 0;
        let right = nums.length - 1;
        // while(left <= right)的终止条件是left == right + 1,
        //写成区间的形式就是[right + 1, right]
        while (left <= right) {
            let middle = left + ((right - left) >> 1);
            // 如果 目标 大于范围 收右边界 [right - 1, right]
            if (nums[middle] > target) {
                right = middle - 1;
            } else { // 寻找右边界,nums[middle] == target的时候更新left
                // [right + 1, right]
				left = middle + 1;
                rightBorder = left;
            }           
        }
        return rightBorder;   
    };

    const getLeftBorder = function(nums, target) {
        let leftBorder = -2;
        let left = 0;
        let right = nums.length - 1;
        while (left <= right) {
            let middle = left + ((right - left) >> 1) 
            if(nums[middle] >= target){ // 寻找左边界,nums[middle] == target的时候更新right
            // [right - 1, right]
                right = middle - 1;
                leftBorder = right;
            } else {
           // 收紧左侧边界时必须 left = mid + 1
// 所以最后无论返回 left 还是 right,必须减一
                left = middle + 1;
            }           
        }
        return leftBorder;
    }
    let rightBorder = getRightBorder (nums, target);
    let leftBorder = getLeftBorder (nums, target);
    if (leftBorder === -2 || rightBorder === -2) return [-1, -1];
    if (rightBorder - leftBorder > 1) return [leftBorder + 1, rightBorder - 1];
    return [-1, -1];
};

简写形式:

var searchRange = function(nums, target) {
    let left = 0, right = nums.length - 1, mid;
    while (left <= right) {//二分查找target
        mid = (left + right) >> 1;
        if (nums[mid] === target) break;
        if (nums[mid] > target) right = mid - 1;
        else left = mid + 1;
    }
    if(left > right) return [-1, -1];
    let i = mid, j = mid;
    while(nums[i] === nums[i - 1]) i--;//向左尝试找相同的元素
    while(nums[j] === nums[j + 1]) j++;//向右尝试找相同的元素
    return [i, j];
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值