字符串、数组、排序算法题

 一、六种排序算法

排序算法 

选择一个目标值,比目标值小的放左边,比目标值大的放右边,目标值的位置已排好,将左右两侧再进行快排。




将大序列二分成小序列,将小序列排序后再将排序后的小序列归并成大序列。




每次排序取一个最大或最小的数字放到前面的有序序列中。




将左侧序列看成一个有序序列,每次将一个数字插入该有序序列。插入时,从有序序列最右侧开始比较,若比较的数较大,后移一位。




循环数组,比较当前元素和下一个元素,如果当前元素比下一个元素大,向上冒泡。下一次循环继续上面的操作,不循环已经排序好的数。




创建一个大顶堆,大顶堆的堆顶一定是最大的元素。交换第一个元素和最后一个元素,让剩余的元素继续调整为大顶堆。从后往前以此和第一个元素交换并重新构建,排序完成。

 
  • 冒泡排序

//思路
循环数组,比较当前元素和下一个元素,如果当前元素比下一个元素大,向上冒泡。

这样一次循环之后最后一个数就是本数组最大的数。

下一次循环继续上面的操作,不循环已经排序好的数。

优化:当一次循环没有发生冒泡,说明已经排序完成,停止循环。

function bubbleSort(array) {
      for (let j = 0; j < array.length; j++) {
        let complete = true;
        for (let i = 0; i < array.length - 1 - j; i++) {
          // 比较相邻数
          if (array[i] > array[i + 1]) {
            [array[i], array[i + 1]] = [array[i + 1], array[i]];
            complete = false;
          }
        }
        // 没有冒泡结束循环
        if (complete) {
          break;
        }
      }
      return array;
    }

//复杂度
时间复杂度:O(n2)

空间复杂度:O(1)

// 稳定性
稳定
  • 快速排序

//实现步骤:

选择一个基准元素target(一般选择第一个数)
将比target小的元素移动到数组左边,比target大的元素移动到数组右边
分别对target左侧和右侧的元素进行快速排序

  function quickSort(array) {
      if (array.length < 2) {
        return array;
      }
      const target = array[0];
      const left = [];
      const right = [];
      for (let i = 1; i < array.length; i++) {
        if (array[i] < target) {
          left.push(array[i]);
        } else {
          right.push(array[i]);
        }
      }
      return quickSort(left).concat([target], quickSort(right));
    }

//记录一个索引l从数组最左侧开始,记录一个索引r从数组右侧开始

//在l<r的条件下,找到右侧小于target的值array[r],并将其赋值到array[l]

//在l<r的条件下,找到左侧大于target的值array[l],并将其赋值到array[r]

//这样让l=r时,左侧的值全部小于target,右侧的值全部小于target,将target放到该位置

不需要额外存储空间,写法思路稍复杂(有能力推荐这种写法)

    function quickSort(array, start, end) {
      if (end - start < 1) {
        return;
      }
      const target = array[start];
      let l = start;
      let r = end;
      while (l < r) {
        while (l < r && array[r] >= target) {
          r--;
        }
        array[l] = array[r];
        while (l < r && array[l] < target) {
          l++;
        }
        array[r] = array[l];
      }
      array[l] = target;
      quickSort(array, start, l - 1);
      quickSort(array, l + 1, end);
      return array;
    }
  • 插入排序

//思路
将左侧序列看成一个有序序列,每次将一个数字插入该有序序列。

插入时,从有序序列最右侧开始比较,若比较的数较大,后移一位。

复杂度

时间复杂度:O(n2)


空间复杂度:O(1)


稳定性:稳定

function insertSort(array) {
      for (let i = 1; i < array.length; i++) {
        let target = i;
        for (let j = i - 1; j >= 0; j--) {
          if (array[target] < array[j]) {
            [array[target], array[j]] = [array[j], array[target]]
            target = j;
          } else {
            break;
          }
        }
      }
      return array;
    }


  • 选择排序

//思路
每次循环选取一个最小的数字放到前面的有序序列中。

//复杂度
时间复杂度:O(n2)

空间复杂度:O(1)

//稳定性
稳定

function selectionSort(array) {
      for (let i = 0; i < array.length; i++) {
        let minIndex = i;
        for (let j = i + 1; j < array.length; j++) {
          if (array[j] < array[minIndex]) {
            minIndex = j;
          }
        }
        [array[minIndex], array[i]] = [array[i], array[minIndex]];
      }
    }
  • 堆排序

//思路
创建一个大顶堆,大顶堆的堆顶一定是最大的元素。

交换第一个元素和最后一个元素,让剩余的元素继续调整为大顶堆。

从后往前以此和第一个元素交换并重新构建,排序完成。

//复杂度
时间复杂度:O(nlogn)

空间复杂度:O(1)

//稳定性
不稳定
function heapSort(array) {
      creatHeap(array);
      console.log(array);
      // 交换第一个和最后一个元素,然后重新调整大顶堆
      for (let i = array.length - 1; i > 0; i--) {
        [array[i], array[0]] = [array[0], array[i]];
        adjust(array, 0, i);
      }
      return array;
    }
    // 构建大顶堆,从第一个非叶子节点开始,进行下沉操作
    function creatHeap(array) {
      const len = array.length;
      const start = parseInt(len / 2) - 1;
      for (let i = start; i >= 0; i--) {
        adjust(array, i, len);
      }
    }
    // 将第target个元素进行下沉,孩子节点有比他大的就下沉
    function adjust(array, target, len) {
      for (let i = 2 * target + 1; i < len; i = 2 * i + 1) {
        // 找到孩子节点中最大的
        if (i + 1 < len && array[i + 1] > array[i]) {
          i = i + 1;
        }
        // 下沉
        if (array[i] > array[target]) {
          [array[i], array[target]] = [array[target], array[i]]
          target = i;
        } else {
          break;
        }
      }
    }
  • 归并排序

//思路
分割:

将数组从中点进行分割,分为左、右两个数组

递归分割左、右数组,直到数组长度小于2

归并:

如果需要合并,那么左右两数组已经有序了。

创建一个临时存储数组temp,比较两数组第一个元素,将较小的元素加入临时数组

若左右数组有一个为空,那么此时另一个数组一定大于temp中的所有元素,直接将其所有元素加入temp

//复杂度
时间复杂度:O(nlogn)

空间复杂度:O(n)

//稳定性
稳定

function mergeSort(array, left, right, temp) {
      if (left < right) {
        const mid = Math.floor((left + right) / 2);
        mergeSort(array, left, mid, temp)
        mergeSort(array, mid + 1, right, temp)
        merge(array, left, right, temp);
      }
      return array;
    }

    function merge(array, left, right, temp) {
      const mid = Math.floor((left + right) / 2);
      let leftIndex = left;
      let rightIndex = mid + 1;
      let tempIndex = 0;
      while (leftIndex <= mid && rightIndex <= right) {
        if (array[leftIndex] < array[rightIndex]) {
          temp[tempIndex++] = array[leftIndex++]
        } else {
          temp[tempIndex++] = array[rightIndex++]
        }
      }
      while (leftIndex <= mid) {
        temp[tempIndex++] = array[leftIndex++]
      }
      while (rightIndex <= right) {
        temp[tempIndex++] = array[rightIndex++]
      }
      tempIndex = 0;
      for (let i = left; i <= right; i++) {
        array[i] = temp[tempIndex++];
      }
    }

 二、数组排序

  • 随机打乱顺序-(洗牌、音乐播放)

function randomSort(arr) {
  // 对数组进行随机打乱,
  // return大于0则交换位置,小于等于0就不交换
  // 由于Math.random()产生的数字为0-1之间的数
  // 所以0.5-Math.random()的是否大于0是随机结果
  // 进而实现数组的随机打乱
  var array = arr.slice();
  array.sort(function () {
    return 0.5 - Math.random();
  })
  // 在控制台输出结果
  console.log(array);
}
// 调用排序方法
randomSort([1,2,3,4,5,6,7]);
  • 打乱数组顺序

//打乱数组顺序,没有重复元素
Fisher–Yates shuffle洗牌算法

Step1:从数组末尾开始遍历,选取当前i位置的元素。然后从0-i随机产生一个位置k,交换a[k]和a[i]
Step2:每次遍历,都只从当前位置前面生成随机位置,因为后面的元素已经乱序
Math.floor(Math.random() * (arr.length - i ))生成0-i的随机位置


var Solution = function(nums) {
  this.nums = nums;
};

/**
 * Resets the array to its original configuration and return it.
 * @return {number[]}
 */
Solution.prototype.reset = function() {
  return this.nums;
};

/**
 * Returns a random shuffling of the array.
 * @return {number[]}
 */
Solution.prototype.shuffle = function() {
  //交换数组元素顺序
  const swap = (a, i, j) => {
    [a[i], a[j]] = [a[j], a[i]];
  };
  let arr = this.nums.slice(); //深拷贝数组,不然会影响reset的输出

  for (let i = arr.length - 1; i >= 0; i--) {
     swap(arr, i, Math.floor(Math.random() * (arr.length - i )));
    //swap(arr, i, Math.floor(Math.random() *i ));
  }
  return arr;
};
  • 最小的K个数

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4
//方法一:sort法
sort正序排序,返回前k个元素:

function GetLeastNumbers_Solution(input, k)
{
    if (input.length < k) return [];
    return input.sort((a,b)=>(a-b)).slice(0,k);
}
  • 合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组
//思路

合并数组的长度为n + m ,从后往前遍历数组,往数组里存入两个数组中的较大的值
数组1的数被存完以后,开始从将数组2的数组依次存入

var merge = function(nums1, m, nums2, n) {
  //if (m === 0 || n === 0) return [].concat(nums1, nums2);

  let [i, j, index] = [m - 1, n - 1, n + m - 1];
  while (i >= 0 && j >= 0) {
    nums1[i] >= nums2[j]
      ? (nums1[index--] = nums1[i--])
      : (nums1[index--] = nums2[j--]);
  }
  while (j >= 0) {
    nums1[index--] = nums2[j--];
  }
};
  • 重复数组排序

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
你能想出一个仅使用常数空间的一趟扫描算法吗

//
方法一:计数法——两次遍历 //思路 首先遍历一遍原数组,分别记录 0,1,2 的个数。 然后更新原数组,按个数分别赋上 0,1,2//方法二:双指针分区法——一次遍历 //思路 定义三个指针:low指向0分区的下一个位置,mid指向1分区的下一个位置,high指向2分区的前一个位置。 如图所示,0区块在前,1区块在中间,2区块在后,中间的?代表尚未遍历的数字,值不确定。 mid指针遍历数组 如果 mid为0,交换low和mid的元素,low和mid都下移一位,0分区增加一个元素 如果 mid为1,mid下移一位,1分区增加一个元素 如果 mid为2,交换high和mid的元素,high向前移动一位,2分区增加一个元素,mid不移动,因为mid元素还需要再进行下一轮比较,不一定是1分区的元素 var sortColors = function(nums) { if (nums.length <= 1) return nums; let low = 0, i = 0, high = nums.length - 1; while (i <= high) { switch (nums[i]) { case 0: swap(nums, i++, low++); break; case 2: swap(nums, i, high--); break; case 1: i++; break; } } return nums; }; var swap = function(a, i, j) { [a[i], a[j]] = [a[j], a[i]]; };
  • 数组奇偶排序

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分

//思路
设定两个指针

第一个指针start从数组第一个元素出发,向尾部前进

第二个指针end从数组的最后一个元素出发,向头部前进

start遍历到偶数,end遍历到奇数时,交换两个数的位置

当start>end时,完成交换

function reOrderArray(array) {
      if (Array.isArray(array)) {
        let start = 0;
        let end = array.length - 1;
        while (start < end) {
          while (array[start] % 2 === 1) {
            start++;
          }
          while (array[end] % 2 === 0) {
            end--;
          }
          if (start < end) {
            [array[start], array[end]] = [array[end], array[start]]
          }
        }
      }
      return array;
    }
  • 把数组排成最小的数

//题目描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。 //思路:排序规则如下: 若ab > ba 则 a > b, 若ab < ba 则 a < b, 若ab = ba 则 a = b; 然后从小到大拼接即为所求结果 function PrintMinNumber(numbers) { // write code here numbers.sort(function(a,b){ var s1=a+''+b; var s2=b+''+a; for(var i=0;i<s1.length;i++){ if(s1.charAt(i)>s2.charAt(i)){ return 1 }else if(s1.charAt(i)<s2.charAt(i)){ return -1; } } return 1 }) var result=""; numbers.map(function(num){ result=result.concat(num) }) return result; }
  • 丑数

编写一个程序判断给定的数是否为丑数。丑数就是只包含质因数 2, 3, 5 的正整数

//
首先,虽然我这个题解可能复杂,但是我觉得我开始的思维是极其简单的。 如果这个 num === 0,那么它不是丑数。 如果这个数 % 2 === 0 或者 % 3 === 0 或者 %5 === 0,那么证明这个数是 2/3/5 的公倍数,那么我们就将它除于 2/3/5,同时这个数还可能是丑数。
(例如 30 / 2 = 15,15 可能是丑数,所以可以继续循环) 循环这个数,直到它不能 %2、%3 或者 %5 得出的结果为 0while 后,这个数已经不能整除 2/3/5 了,所以我们最终判断它的值为多少。如果是 2/3/5,表明它是丑数;如果不是,则它不是丑数。 var isUgly = function(num) { if (!num) { return false; } while (num % 2 === 0 || num % 3 === 0 || num % 5 === 0) { if (num % 2 === 0) { num = num / 2; } if (num % 3 === 0) { num = num / 3; } if (num % 5 === 0) { num = num / 5; } } if (num === 1) { return true; } if (num !== 2 || num !== 3 || num !== 5) { return false; } return true; };
  • 数组中的逆序对

//在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
//思路:利用归并排序思想,先求出每个组中逆序对数,然后合并、排序并统计 function InversePairs(data) { if(!data||data.length<2) return 0; var copy = data.slice(), count = 0; count = mergeSort(data,copy,0,data.length-1); return count%1000000007; } function mergeSort(data,copy,start,end){ if(end===start) return 0; var mid = (end-start)>>1, left = mergeSort(copy,data,start,start+mid), right = mergeSort(copy,data,start+mid+1,end), count = 0, p = start+mid,//前一个数组的最后一个下标 q = end,//后一个数组的下标 copyIndex = end;//辅助数组下标,从最后一个算起 while(p>=start&&q>=start+mid+1){ if(data[p]>data[q]){ count+=q-start-mid; copy[copyIndex--] = data[p--]; }else{ copy[copyIndex--] = data[q--]; } } while(p>=start){ copy[copyIndex--] = data[p--]; } while(q>=start+mid+1){ copy[copyIndex--] = data[q--]; } return left+right+count; }
  • 移动零

//给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
//类似于冒泡排序。如果是0就后移
var moveZeroes = function (nums) {
  for (let i = 0; i < nums.length; i++) {
    for (let j = 0; j < nums.length - i - 1; j++) {
      if (nums[j + 1] !== 0 && nums[j] === 0) {
        nums[j] = nums[j + 1];
        nums[j + 1] = 0;
      }
    }
  }

  return nums;
}
  • 两个数组的交集

//在本题中,我们将 nums1 的值,都存储到 Map 中。

然后,遍历 nums2,如果 Map 中包含这个元素,并且数组 result 中不存在这个元素,我们就将它存入到数组中。

最后,根据前面的步骤,我们做到了去重 + 取交集。
var intersection = function(nums1, nums2) {
  let map = new Map();
  let result = [];
  for (let i = 0; i < nums1.length; i++) {
    map.set(nums1[i], i);
  }
  for (let j = 0; j < nums2.length; j++) {
    if (map.get(nums2[j]) !== undefined && result.indexOf(nums2[j]) === -1) {
      result.push(nums2[j]);
    }
  }
  return result;
};

三、数组去重

  • 对排序数组去重

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度

//
思路 给定的数组 nums是一个排过序的数组,那么,重复元素一定是在数组中相邻的元素。也就是说,我们可以通过遍历数组,找出相邻相同项,并将其从数组中移除即可 需要原地删除,那么我们就不能创建一个副本,而是只能在原数组上进行操作 参考:https://blog.csdn.net/qq_30216191/article/details/81348501 特殊情况 数组长度小于等于1,return 遍历数组,比较当前和下一位 如果相等删除一位,并把i--,否则会跳过下个元素 遍历完成,返回数组长度 var removeDuplicates = function(nums) { if (nums.length <= 1) { return nums.length; } for (let i = 0; i < nums.length; i++) { if (nums[i] === nums[i + 1]) { nums.splice(i, 1); i--; } } return nums.length; }; removeDuplicates([1, 1, 2]);
  •  检查是否存在重复元素

//方法1、对象键值法
var containsDuplicate = function(nums) {
    if(nums.length<=1){
        return false;
    }
      let obj = {};//对照对象
  for (var i = 0; i < nums.length; i++) {
    // 判断当前项是否遍历过,是则删除,否存入obj以作对照

    if (obj[nums[i]]) {
      return true
      //数组删除了一项,要把i回退一下,不然会跳过下一项不去遍历
    } else {
      obj[nums[i]] = 1;
    }
  }
    return false;
   
};

//方法二:set法,比较去重数组和原数组的长度
var containsDuplicate = function(nums) {
    if(nums.length<=1){
        return false;
    }
      
    let uniq=[...new Set(nums)];
    return !(uniq.length===nums.length);
    
};
  • 检查相邻k个元素是否存在重复元素

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k。

//
some() 方法会依次执行数组的每个元素: 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。 如果没有满足条件的元素,则返回false。 array.some(function(currentValue,index,arr),thisValue) //includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。 arr.includes(searchElement, fromIndex) //是否存在重复元素2 /** * @param {number[]} nums * @param {number} k * @return {boolean} */ var containsNearbyDuplicate = function(nums, k) { //some遍历得到第一个函数的返回值为true,就会return true,如果q return nums.some((item, index) => { return nums.slice(index + 1, index + k + 1).includes(item); }); };

四、查找数组中的元素

  • 大数相加

function add(n, m) {
  // 操作数类型判断, 要求为字符串。不满足条件则抛出异常。
  if (typeof n !== 'string' || typeof m !== 'string') {
    throw new Error('数据类型错误, 大数相加操作数为字符串!');
  }

  // 数据反转, 方便后面的遍历求和
  n = n.split('').reverse();
  m = m.split('').reverse();

  // 获取较长的位数, 并作为后面对应位数遍历的最大值
  const maxLength = Math.max(n.length, m.length);

  // 计算过程中, 临时变量
  let tempN = 0; // 临时存储第1个操作数
  let tempM = 0; // 临时存储第2个操作数
  let tempAdd = 0; // 临时存储对应位上的数相加的和
  let extNum = 0; // 满10进1,保存进的值(1 或 0)

  // 计算结果
  const res = []; // 应位上的书相加的和

  // 遍历每一位上的数字,并求和。记录满十进一
  for (let index = 0; index < maxLength; index++) {
    // 缺位补0
    tempN = n[index] || 0;
    tempM = m[index] || 0;

    // 对应位上的数字求和
    tempAdd = Number(tempN) + Number(tempM);

    // 进一(extNum 为进 1)
    if (extNum) {
      tempAdd += extNum;
    }

    // 满十(存储需要进的 1)
    extNum = tempAdd >= 10 ? 1 : 0;

    // 最后一位满十进的一直接保存在当前求得的和中, 非最后一位则取 %10 后的值
    if (index === (maxLength - 1) && extNum) {
      res.push(tempAdd);
    } else {
      res.push(tempAdd % 10);
    }
  }

  // 返回计算后的数时注意翻转
  return res.reverse().join('');
}

 

  • 找出数组中第二大的值

var arr=[5,2,10,8,0,4,7,11,9,1];
function array2(){
    var temp,min;
    for(var i=0;i<arr.length-1;i++){
        min=i;
        for(var j=i+1;j<arr.length;j++){
            if(arr[j]>arr[i]){  
                temp= arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
    alert(arr[1]);
}
array2();
  • 查找数组中是否包含某些特定字符串

function search(Arr,str){
  var newArr = [];
  for(var k in Arr){
    if(Arr[k].indexOf(str) != -1){
      newArr.push(Arr[k]);
    }
  }
  return newArr.length !=0 ? newArr : "未找到";
}
 
var arr = ["abc","acd","bcf"];
var str1 = "b"; 
var result = search(arr,str1);
console.log(result);//["abc","bcf"]
 
var str2 = "s";
var result1 = search(arr,str2);
console.log(result1);//"未找到"
  • 字符串里面最长的数字串

 //利用正则匹配找出数字
var str="1a12ab123abc1234abcd12345abcde123456abcdef";
var n=[];
    var newStr=str.replace(/\d+/g, function () {
        //调用方法时内部会产生 this 和 arguments
    return n.push(arguments[0]);
    });
var i=0;
for(let j=0;j<n.length;j++){
    if(n[j].length>=i){
        i=n[j].length;
        var max=j;
    }
}
console.log(n[max]);
console.log(i);

 

  • 两数之和 

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标
//解法一:for()
//思路
第一遍过滤 nums 数组,标记为 i。
第二遍再次过滤 nums 数组,标记为 i + 1,因为我们是对数组中的两个数字相加,所以不能重复使用同一个数字。
判断第二次遍历的数字中,它是否等于 target - nums[i],如果成立就返回两个数字的索引。(并不考虑后面还有可成立的答案)。

var twoSum = function(nums, target) {
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[j] === target - nums[i]) {
        return [i, j];
      }
    }
  }
};

//解法2:indexOf()
//思路
首先,我们开辟一块内存 result。

然后,我们通过 map() 遍历 nums,并使用 indexOf() 寻找除当前 item 的 index 之外和 item 相加之和为 target 的结果。

最后,我们返回查找的最新结果,该结果进行了排序([4, 2] 的返回通过 sort() 排序变成 [2, 4])
var twoSum = function(nums, target) {
  let result = [];
  nums.map((item, index) => {
    if (nums.indexOf(target - item) > -1 && nums.indexOf(target - item) != index) {
      result = [index, nums.indexOf(target - item)].sort((a, b) => a > b);
    }
  });
  return result;
};

//解法3:map
//思路
首先,我们需要了解 Map 这个对象。

它可以通过 set() 的形式,以 [key, value] 的形式保存一组数据。(题目中对应 key 就是存入的 target - nums[i] 值,value 就是索引)
它可以通过 get() 的形式,获取到传入 key 值对应的 value。
它可以通过 has() 的形式,判断 Map 对象里面是否存储了传入 key 对应的 value。
然后,我们遍历 nums 数组。

最后,我们判断 nums[i] 是否存在于 Map 对象中。没有的话,就存入 target - nums[i] 到 Map 中。
有的话,因为上次存入的是 target- nums[i],有点类似于解题的钥匙,既然我们看到 nums[i] 存在于 Map 中,
它是解题的钥匙,所以我们只需要返回 [map.get(nums[i]), i] 这组值即可。
var twoSum = function(nums, target) { let map = new Map(); for (let i = 0; i < nums.length; i++) { if (map.has(nums[i])) { return [map.get(nums[i]), i]; } else { map.set(target - nums[i], i); } } };
  • 数组中出现次数超过一半的数字

//思路

特殊情况:长度为1/0
新建map,遍历数组,依次储存数组值和频率
如果map里有item这一项,频率value+1,并判断是否超过Math.floor(numbers.length / 2),超过则return,
注意需要循环内return,所以必须使用for循环,不能使用foreach
如果map里没有item这一项,频率value为1
循环结束没有返回,说明数组不存在这样的元素

function MoreThanHalfNum_Solution(numbers) {
  // write code here
  if (numbers.length === 0) return 0;
  if (numbers.length === 1) return numbers[0];
  let map = new Map();
  for (let i = 0; i < numbers.length; i++) {
    //map里有item这一项
    if (map.has(numbers[i])) {
      map.set(numbers[i], map.get(numbers[i]) + 1); //频率value+1
      if (map.get(numbers[i]) > Math.floor(numbers.length / 2))
        return numbers[i]; //频率value>一半长度,返回
    }
    //map里没有item这一项
    else {
      map.set(numbers[i], 1);
    } //频率value为1
  }
  //循环结束没有返回,说明数组不存在这样的元素
  return 0;
}
MoreThanHalfNum_Solution([1, 2, 3, 2, 2, 2, 5, 4, 2]);
  • 数组旋转


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


//
思路 数组可以被分成两个不减的子数组,最小值就是第二个子数组的开头元素,使用二分法寻找这个元素,使用三个指针:左指针,右指针,中间指针。 没有重复元素的数组,比较mid和right的大小, 如果mid小于right,说明mid-right是单调递增(这中间不会有最小值),min在left~mid中间; 如果mid大于right,说明mid-right不是单调递增(mid不会是最小值,但是这中间会有最小值),min在mid+1~right中间; mid=right,说明存在重复元素,不能直接判断单调性,右指针左移一位,依次比较。 function minNumberInRotateArray(rotateArray) { // write code here // 空数组/单元素数组 if (!rotateArray || rotateArray.length === 1) { return rotateArray[0] || 0; } let left = 0, //左指针 right = rotateArray.length - 1; //右指针 while (left < right) { let mid = Math.floor((left + right) / 2); //mid和right相等,最小值一定在right元素的右边 if (rotateArray[mid] === rotateArray[right]) { right--; //右指针左移动一位,依次比较 } //mid-right非递增,最小值一定在mid元素的右边 else if (rotateArray[mid] > rotateArray[right]) { left = mid + 1; //左指针移动到mid右边第一位 } //mid-mid递增,最小值一定在mid/mid元素的 else right = mid; //右指针移动到mid } // left和right相遇退出循环,该位置就是最小值 return rotateArray[right]; }
  • 统计一个数字在排序数组中出现的次数。

function GetNumberOfK(data, k)
{
    // write code here
    var start = data.indexOf(k),
        count = 0,
        i = start;
    while(i < data.length && data[i] == k){
        count++;
        i++;
    }
    return count;
}

//二分查找方法:先看中间元素与k相比,小于则在左半部分,大于则在右半部分
function GetNumberOfK(data, k)
{
    // write code here
    var l = 0,r = data.length,mid;  
    while(l < r){  
        mid = Math.floor((l+r)/2);  
        if(data[mid] < k ){  
            while(data[mid] == data[mid+1]){  
                mid++;  
            }  
            l = ++mid;  
        }else if(data[mid] > k){  
            while(data[mid] == data[mid-1]){  
                mid--;  
            }  
            r = --mid;  
        }else{  
            var sign1 = mid,sign2 = mid;  
            while(sign1 <= r && data[sign1] == data[sign1+1]){  
                sign1++;  
            }  
            while(sign2 >= l && data[sign2] == data[sign2-1]){  
                sign2--;  
            }  
            return sign1-sign2+1;  
        }  
    }  
    return 0;  
}
  • 数组中只出现一次的两个数字

//思想

第一步: 把所有的元素进行异或操作,最终得到一个异或值。因为是不同的两个数字,所以这个值必定不为0;

第二步: 取异或值最后一个二进制位为1的数字作为mask,如果是1则表示两个数字在这一位上不同。

第三步: 通过与这个mask进行与操作,如果为0的分为一个数组,为1的分为另一个数组。
这样就把问题降低成了:“有一个数组每个数字都出现两次,有一个数字只出现了一次,求出该数字”。对这两个子问题分别进行全异或就可以得到两个解。也就是最终的数组了。
var singleNumber = function (nums) { if (nums.length < 2 || !nums) { return } if (nums.length === 2) { return nums } let xor = 0 for (let i = 0; i < nums.length; i++) { xor ^= nums[i] } let lastOne = -xor & xor let numOne = 0 let numTwo = 0 let arr1 = [] let arr2 = [] for (let i = 0; i < nums.length; i++) { if ((nums[i] & lastOne) === 0) { numOne ^= nums[i] } else { numTwo ^= nums[i] } } return [numOne, numTwo] }
  • 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

//解题思路:
首先,我们需要了解的是,一个数 / 2,大概率返回的是小数,而我们的索引需要的是整数,所以我们通过 Math.round() 来四舍五入获取整数。

然后,就是 while 的逻辑判断:

nums:[1, 3, 5, 6]
target:2
最后,我们需要知道的是,如果 target 是 2,那么返回的 [left, right] 是:[1, 0];
如果 target 是 4,那么返回的 [left, right] 是 [2, 1]。因为循环结束的条件是 left > right,所以无疑 left 是更接近中间值的。 var searchInsert = function(nums, target) { let left = 0; let right = nums.length - 1; while (left <= right) { let middle = Math.round((left + right) / 2); if (target === nums[middle]) { return middle; } else if (target < nums[middle]) { right = middle - 1; } else if (target > nums[middle]) { left = middle + 1; } } return left; };
  • 盛水最多的容器

var maxArea = function(height) {
    
    let max = 0
    for (let i = 0, j = height.length-1; i< j; ) {
         // 因为容量受限于矮的一边,所以在向内收缩时,移动矮的一侧
         // 如果新边足够高的话,效果有可能大于宽度收缩带来的负效果
        let minHeight = height[i] > height[j] ? height[j--] : height[i++]
        // 因为上面--或者++了,所有要补个+1
        max = Math.max(max, (j-i+1)* minHeight )
    }
    return max

};
  • 数组的最小移动次数

给定一个长度为 n 的非空整数数组,找到让数组所有元素相等的最小移动次数。每次移动可以使 n - 1 个元素增加 1
//思路
移动次数等于所有值减去最小值。
var minMoves = function(nums) {
  let min =  Math.min.apply(null, nums);
  let count = 0;
  for (let i = 0; i < nums.length; i++) {
    count += nums[i] - min;
  }
  return count;
};
  • 孩子们的游戏(圆圈中最后剩下的数) 

//题目描述
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。
HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:
首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。
每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数….这样下去….
直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)


//思路1:创立数组,每当到m-1位置时,删除数组中元素,然后将位置计0重新开始
function LastRemaining_Solution(n, m)
{
    // write code here
    if(n<1||m<1) return -1; 
    var arr=[];
    for(var i=0;i<n;i++) arr.push(i);
    var idx=0;
    var start=0;
    while(arr.length>1){
        for(var i=1;i<m;i++){//找到第m个位置
            idx=(idx+1)%arr.length;
        }
        arr.splice(idx,1)       
    } 
    return arr[0];
}

//方法2
function LastRemaining_Solution(n, m)
{
    var last=0;
    for(var i=2;i<=n;i++){
        last=(last+m)%i
    }
    return last
}
  • 二维数组中的查找

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数

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

五、数组子序列

  • 连续子数组的最大和

输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)
//思路:

找最大子序列之和,等价于一段找连续的子序列,和是最大值,因为和为负的子序列会减少和,所以直接抛弃这个子序列和更大
特殊情况:数组为空/单元素
数组全是负数时,最大和为数组中的最大值
数组有正数时,从头到尾逐个累加数组中的每个数字,存放到curSum,如果curSum小于0,抛弃之前的累加值,重新开始计数,curSum=item;
比较curSum和 maxSum,取最大值,存进 maxSum,为当前历史序列和的最大值

function FindGreatestSumOfSubArray(array)
{
    // write code here
     //数组为空/单元素
  if (array.length === 0) return 0;
  if (array.length === 1) return array[0];
  //全是负数时,最大和为数组中的最大值
  if(array.every(item => item < 0)) {
      return Math.max(...array);
  }
  //数组有正数时,要遍历比较最大和
 
    let curSum,
      maxSum = 0;
    array.forEach((item, index) => {
      curSum = curSum > 0 ? curSum + item : item;
      maxSum = Math.max(curSum, maxSum);
    });
    return maxSum;
 
  
}
  •  买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),
设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。
//思路:

等价于找一个数组的两个元素子序列,使得他们差值最大,要求小元素在前,大元素在后
遍历数组,i指向卖出天数
maxProfit保存最大利润,是0~i-1之间的差值的最大值,每次最大值只能在当前元素-当前元素之前的最小买入价以及上次保存的最大利润中产生。
因为要使用当前元素之前的minPrice,所以先比较更新maxProfit minPrice保存最小买入价,是0~i
-1之间的最小值,每次比较当前值和上次保存的最小值 遍历完成以后的 maxProfit就是整个数组中的最大差值 var maxProfit = function(prices) { if (prices.length <= 1) return 0; let minPrice = prices[0], //最小买入价,初始化为第一个价格 maxProfit = 0; //最大利润,初始为0 for (let i = 0; i < prices.length; i++) { //和当前价和最小买入价之差比较,更新最大利润 maxProfit = Math.max(maxProfit, prices[i] - minPrice); //和当前价格比较,更新最小买入价 minPrice = Math.min(minPrice, prices[i]); } return maxProfit; };
  • 买卖股票的最佳时机

    给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。
    你可以尽可能地完成更多的交易(多次买卖一支股票)
    //思路
    
    比较数组相邻元素,后一位比较大,就把差值累加到结果上
    var maxProfit = function(prices) {
      if (prices.length <= 1) return 0;
      let res=0;
      for (let i = 1; i < prices.length; i++) {
        prices[i] > prices[i - 1] ? (res += prices[i] - prices[i - 1]) : "";
      }
      return res;
    };
  • 输入一个正数S,打印出所有和为S的连续正数序列。

例如:输入15,有序1+2+3+4+5 = 4+5+6 = 7+8 = 15 所以打印出3个连续序列1-5,5-6和7-8//思路
创建一个容器child,用于表示当前的子序列,初始元素为1,2

记录子序列的开头元素small和末尾元素big

big向右移动子序列末尾增加一个数 small向右移动子序列开头减少一个数

当子序列的和大于目标值,small向右移动,子序列的和小于目标值,big向右移动
function FindContinuousSequence(sum) {
      const result = [];
      const child = [1, 2];
      let big = 2;
      let small = 1;
      let currentSum = 3;
      while (big < sum) {
        while (currentSum < sum && big < sum) {
          child.push(++big);
          currentSum += big;
        }
        while (currentSum > sum && small < big) {
          child.shift();
          currentSum -= small++;
        }
        if (currentSum === sum && child.length > 1) {
          result.push(child.slice());
          child.push(++big);
          currentSum += big;
        }
      }
      return result;
    }
  • 杨辉三角

//给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。

在杨辉三角中,每个数是它左上方和右上方的数的和。

function combination(m,n){
    if(n == 0) return 1;//第一个数为1
    else if(m == n) return 1; //最后一个数为1
    else return combination(m-1,n-1)+combination(m-1,n);//中间的数为前一行的两个数相加
}
function Print(n){ 
    for( var i = 0 ; i < n ; i++ ){ 
        let arr=[];//用来放第i行的数
        for ( var j = 0 ; j <= i ; j++ ) {
            arr.push(combination(i,j));
        }
            console.log(arr.join(' '));//字符串形式输出
}
  • 给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。在杨辉三角中,每个数是它左上方和右上方的数的和。

    //思路
    如果 rowIndex 为 0,返回 [1]
    如果 rowIndex 为 1,返回 [1, 1]
    递归 getRow(rowIndex - 1),然后求出中间的和,并 return 出来 [1, ...result, 1]
    
    var getRow = function(rowIndex) {
      if (!rowIndex) {
        return [1];
      }
      if (rowIndex === 1) {
        return [1, 1];
      }
      let recursion = getRow(rowIndex - 1);
      let result = [];
      for (let i = 0; i < rowIndex; i++) {
        if (recursion[i] && recursion[i + 1]) {
          result.push(recursion[i] + recursion[i + 1]);
        }
      }
      return [1, ...result, 1];
    };

六、字符串

  • 1+2+3+……+n

//求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

function Sum_Solution(n)
{
    // write code here
    var sum=n;
    //短路运算,n!==0,如果等于0,则不会进行后续的运算
    var a=(n!=0)&&((sum+=Sum_Solution(n-1))>0)
    return sum
}
  • 不用加减乘除做加法

//题目描述
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

//思路:不考虑进位直接相加可以看出,1+0=1,1+1=0,0+0=0,这是异或操作(^),
对加 0 、0 加 1 、1 加 0 而言, 都不会产生进位,只有 1 加 1 时,会向前产生一个进位。此时我们可以想象成是两个数先做位与运算,然后再向左移动一位
function Add(num1, num2) { // write code here var sum,carry; do{ sum=num1 ^ num2; carry=(num1&num2)<<1; num1=sum; num2=carry; }while(num2!=0) return sum; }
  • 整数反转

//思路
首先,将传入的数字 x 转换成字符串,并分割成数组。

然后,遍历该数组,将最后一个取出来放到 result 中。

最后,判断这个 result 是否超过题目限制,如果超过则变成 0var reverse = function(x) {
  // 转数组
  let numberToArray = String(Math.abs(x)).split('');
  
  // 转字符串
  let result = '';
  for (const i = 0; i < numberToArray.length; ) {
    result += numberToArray.pop();
  }
  result = x > 0 ? Number(result) : - Number(result);
  
  // 超 [-Math.pow(2, 31), Math.pow(2, 31) - 1] 判断
  if (result > Math.pow(2, 31) - 1
  || result < - Math.pow(2, 31)) {
    result = 0;
  }
  
  return result;
};
  • 罗马数字转整数

//思路
首先,设置 Map,将正常情况存下来。

然后,遍历字符串,判断特殊情况,如果是特殊情况,需要跳过下一次循环,否则直接获取 Map 中对应的值。

最后,将结果通过 result 给 return 出去。
var romanToInt = function(s) {
  /**
   * 特殊情况
   * IV === 4
   * IX === 9
   * XL === 40
   * XC === 90
   * CD === 400
   * CM === 900
   * 正常情况
   * I === 1
   * V === 5
   * X === 10
   * L === 50
   * C === 100
   * D === 500
   * M === 1000
   */
  let map = new Map();
  map.set('I', 1);
  map.set('V', 5);
  map.set('X', 10);
  map.set('L', 50);
  map.set('C', 100);
  map.set('D', 500);
  map.set('M', 1000)
  
  let result = 0;
  for (let i = 0; i < s.length; i++) {
    if (s[i] + s[i+1] === 'IV') {
      result += 4;
      i = i + 1;
    } else if(s[i] + s[i+1] === 'IX') {
      result += 9;
      i = i + 1;
    } else if(s[i] + s[i+1] === 'XL') {
      result += 40;
      i = i + 1;
    } else if(s[i] + s[i+1] === 'XC') {
      result += 90;
      i = i + 1;
    } else if(s[i] + s[i+1] === 'CD') {
      result += 400
      i = i + 1;
    } else if(s[i] + s[i+1] === 'CM') {
      result += 900;
      i = i + 1;
    } else {
      result += map.get(s[i]);
    }
  }
  
  return result;
};
  • 快乐数

//编写一个算法来判断一个数是不是“快乐数”。

一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,
然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。
如果可以变为 1,那么这个数就是快乐数。
//思路
在 1-9 中,1 和 7 是快乐数,即它们计算后的最终结果是 1。除此之外,都不是快乐数,返回 false。
一个数,假设是 89。那么,第一次遍历,将它磨 10,得到的是个位数 9;除以 10(JS 没有强类型,需要借助 Math.floor()),那么缩短 1 位变成 8。
第二次遍历,将它磨 10,得到 8,再除以 10,得到的数小于 1,终止循环。 在这过程中,我们得到 sum = 9 * 9 + 8 * 8,即将一个数彻底分解。递归往复,在结果为 10 之内的时候,就可以返回 true 或者 false 了。 var isHappy = function(n) { if (n == 1 || n == 7) { return true; } if (n < 10) { return false; } let sum = 0; while (n >= 1) { let d = n % 10; sum += d * d; n = Math.floor(n / 10); } return isHappy(sum); };
  • 缺失数

给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数
//直接对前n项进行求和与从1到n的数字之和相比,返回差值即可

var missingNumber = function(nums) {
    let n = nums.length;
    let sum1 = (n+1)*(n)/2;
    let sum=0;
    for(let i=0;i<n;i++)
        {
            sum += nums[i];
        }
    return sum1-sum;
};
  • js根据数组中对象的某个属性进行排序

    let arr = [
        {name: '张三',age: 18},
        {name: '李四',age: 9},
        {name: '王五',age: 28}
    ]
    const handle = (property) => {
        return function(a,b){
            const val1 = a[property];
            const val2 = b[property];
            return val1 - val2;
        }
    }

    arr.sort(handle('age'));
  • 同构字符串

//给定两个字符串 s 和 t,判断它们是否是同构的。

如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。

所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。
两个字符不能映射到同一个字符上,但字符可以映射自己本身。

//思路
ndexOf():判断数组中是否存在判断条件中的值。如果存在,则返回第一次出现的索引;如果不存在,则返回 -1var isIsomorphic = function(s, t) {
  for (let i = 0; i < s.length; i++) {
    if (s.indexOf(s[i]) !== t.indexOf(t[i])) {
      return false;
    }
  }
  return true;
};
  • 统计一个字符串出现最多的字母

function findMaxDuplicateChar(str) {  
  if(str.length == 1) {
    return str;
  }
  let charObj = {};
  for(let i=0;i<str.length;i++) {
    if(!charObj[str.charAt(i)]) {
      charObj[str.charAt(i)] = 1;
    }else{
      charObj[str.charAt(i)] += 1;
    }
  }
  let maxChar = '',
      maxValue = 1;
  for(var k in charObj) {
    if(charObj[k] >= maxValue) {
      maxChar = k;
      maxValue = charObj[k];
    }
  }
  return maxChar;

}
  • 判断括号是否闭合合法

//思路
遇到左括号进行入栈,遇到右括号判断栈顶元素是否与当前字符匹配,如果匹配则出栈,否则返回false,
最后检查栈是否为空,如果为空说明完全匹配,返回true,否则返回false。JavaScript实现利用数组的push和pop方法实现元素的入栈和出栈。
var isValid = function(s) { var sub = []; var len = s.length; for (var i = 0; i < len; i++) { if (s[i] === '(' || s[i] === '[' || s[i] === '{') { //遇到左括号添加进数组 sub.push(s[i]); } else if (s[i] === ')') { if (sub[sub.length - 1] === '(') { //遇到右括号判断数组最后一个元素是否与当前元素匹配 sub.pop(); //匹配则删除数组最后一个元素 } else { return false; //否则返回false } } else if (s[i] === ']') { if (sub[sub.length - 1] === '[') { sub.pop(); } else { return false; } } else { if (sub[sub.length - 1] === '{') { sub.pop(); } else { return false; } } } if (sub.length == 0) return true; else return false; }; var result = isValid('()');
  •  判断一个字符串是不是回文字符串

var isPalindrome = function(x) {
    if(x >= 0){
       var strx = String(x); 
       return strx == strx.split("").reverse().join("");
    }
    return false;
};
  • 斐波那契数列

function getFibonacci(n) {  
  var fibarr = [];
  var i = 0;
  while(i<n) {
    if(i<=1) {
      fibarr.push(i);
    }else{
      fibarr.push(fibarr[i-1] + fibarr[i-2])
    }
    i++;
  }

  return fibarr;
}
  • 跳台阶

function jumpFloor(n)
{
    if(n<=2){
        return n;
    }
    let i = 2;
    let pre = 1;
    let current = 2;
    let result = 0;
    while(i++ < n){
        result = pre + current;
        pre = current;
        current = result;
    }
    return result;
}
  • 字符串反转

function ReverseSentence(str)
{
    if(!str){return ''}
    return str.split(' ').reverse().join(' ');
}
  • 输入一个字符串,按字典序打印出该字符串中字符的所有排列

例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cabcba
//使用回溯法

记录一个字符(temp),用于存储当前需要进入排列的字符

记录一个字符串(current),用于记录当前已经排列好的字符

记录一个队列(queue),用于存储还未被排列的字符

每次排列将temp添加到current
如果queue为空,则本次排列完成,将curret加入到结果数组中,结束递归
如果queue不为空,说明还有未排列的字符
递归排列queue中剩余的字符
为了不影响后续排列,每次递归完成,将当前递归的字符temp加回队列

function Permutation(str) {
      const result = [];
      if (str) {
        queue = str.split('')
        PermutationCore(queue, result);
      }
      result.sort();
      return [... new Set(result)];
    }

    function PermutationCore(queue, result, temp = "", current = "") {
      current += temp;
      if (queue.length === 0) {
        result.push(current);
        return;
      }
      for (let i = 0; i < queue.length; i++) {
        temp = queue.shift();
        PermutationCore(queue, result, temp, current);
        queue.push(temp);
      }
    }
  • 随机生成指定长度的字符串

function randomString(n) {  
  let str = 'abcdefghijklmnopqrstuvwxyz9876543210';
  let tmp = '',
      i = 0,
      l = str.length;
  for (i = 0; i < n; i++) {
    tmp += str.charAt(Math.floor(Math.random() * l));
  }
  return tmp;
}
  • 不借助临时变量,进行两个整数的交换

//主要是利用 + - 去进行运算,类似 a = a + ( b - a) 实际上等同于最后 的 a = b;

function swap(a , b) {  
  b = b - a;
  a = a + b;
  b = a - b;
  return [a,b];
}

module.exports = swap;  
  • 替换空格

请实现一个函数,将一个字符串中的空格替换成“%20”
//思路:使用正则表达式,\s+代表多个空格,?则表示取尽可能少的空格,然后通过replace函数替换为%20

function replaceSpace(str)
{
    // write code here
    return str.replace(/\s+?/g,'%20')
}
  • 字符流中第一个不重复的字符

//思路:设置一个数组存放初次出现的元素。如果这个元素没有过,就放进数组;如果这个元素有过了,就把他从数组删除。输出数组的第一个元素。
var map = {};
//Init module if you need
function Init()
{
    // write code here
    map = {};
}
//Insert one char from stringstream
function Insert(ch)
{
    // write code here
    if (!map[ch]) {
        map[ch] = 1;
    } else {
        map[ch] ++;
    }
}
//return the first appearence once char in current stringstream
function FirstAppearingOnce()
{
    for (var ch in map) {
        if (map.hasOwnProperty(ch)) {
            if (map[ch] === 1) {
                return ch;
            }
        }
    }
    return '#';
}
  • 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

//思路
那么,我们就有思路破解本题了:

如果出现的第一个位置和出现的最后一个位置相同,那么就是唯一的,返回 i。
如果都不是相同的,说明没有唯一值,返回 -1var firstUniqChar = function(s) {
  for (let i = 0; i < s.length; i++) {
    if (s.indexOf(s[i]) === s.lastIndexOf(s[i])) {
      return i;
    }
  }
  return -1;
};
  • 最长公共前缀

//思路
通过 reduce(),我们可以进行一项累加操作:先比较第一项和第二项,然后找到它们共通值后,剪切并 return;再比较的时候,
使用 return 出来的值和第三项进行比较……依次类推 最后,返回最后一次 return 的值。 var longestCommonPrefix = function(strs) { if (strs.length < 2) { return !strs.length ? '' : strs[0]; } return strs.reduce((prev, next) => { let i = 0; while (prev[i] && next[i] && prev[i] === next[i]) { i++; }; return prev.slice(0, i); }); };
  • 最长回文子序列

//https://juejin.im/post/5d39d3e16fb9a07ec42ba020

var longestPalindrome = function(s) {
    var start = end = 0
    for(let i = 0 ; i < s.length; i++){
        let len1 = search(s,i,i)
        let len2 = search(s,i,i+1)
        
        let len = Math.max(len1,len2)
        if(len > end -start){
            start = i - Math.floor((len - 1) / 2)
            end = i + Math.floor(len / 2)
        }   
    }
    return s.slice(start,end+1) 

    function search(s,l,r){
        let left = l,
            right = r;
        while(left >= 0 && right < s.length && s[left] === s[right]){
            left--;
            right++;
        }
        return right - left - 1;
    }
};

 

  • 字符串相加

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和
//思路
如果没有超过最大精度限制,即它们两个相加数字的长度小于 16,我们直接使用 JavaScript 自带的加法运算。
如果超过最大精度限制,那么就实施方案二。
先补位。为什么要补位呢?我们的计算都是从个位数相加的,即 最后一位对齐 原则,通过补位,我们可以将 1234 变成 01234,从而能够跟 12345 对齐相加。
遍历相加。遍历的两个数字,如果相加大于 10,那么前面一位就需要 + 1,我们通过 temp 来记录,同时,如果是最后一个字符串,我们需要给前面的补字符串 1;
如果相加小于 10,说明可以直接添加到字符串中,同时,我们需要清空掉补位数(即 temp)。 var addStrings = function(num1, num2) { // 如果没有超过 JS 最大精度限制 if (num1.length < 16 && num2.length < 16) { return String(Number(num1) + Number(num2)); } // 如果超过 JS 最大精度限制 // 1. 补位:12345 与 01234 let patch = ''; const differenceLength = num1.length > num2.length ? num1.length - num2.length : num2.length - num1.length; for (let i = 0; i < differenceLength; i++) { patch += '0'; } if (num1.length > num2.length) { num2 = patch + num2; } else if(num1.length < num2.length) { num1 = patch + num1; } // 2. 计算相加 let temp = 0; // 补进制 let result = ''; for (let i = num1.length - 1; i >= 0; i--) { let sum = Number(num1[i]) + Number(num2[i]) + temp; if (sum >= 10) { result = sum % 10 + result; // 如果超 10 记得补 1 temp = 1; // 如果在最后一个字符串了,那么记得给前面补 1 if (i === 0) { result = '1' + result; } } else { result = sum + result; temp = 0; // 如果没超 10 记得清 0 } } return result; }; //大神解答 var addStrings = function (num1, num2) { let a = num1.split(''); let b = num2.split(''); let c = 0; let res = ''; while (a.length || b.length || c) { c += ~~a.pop() + ~~b.pop(); res = c % 10 + res; c = c > 9; } return res; };
  •  重复的子字符串

给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000
//思路
/^(\w+)\1+$/.test(s)

(\w+) 表示任意多个字母或数字或下划线,也就是 AZ,az,0~9,_ 中任意多个组成。
\1+ 表明前面的字母可能重复 1+ 次数。
这样,正则表达式直接通过 test() 匹配 s,返回 true 或者 false
const repeatedSubstringPattern
= (s) => { return /^(\w+)\1+$/.test(s); };
  • 把字符串转换成整数

function StrToInt(str)
{
    // write code here
    if(str==null || str.length<=0) return 0;
    var len=str.length,index=0,flag=false;
    var arr=str.split('')
    if(arr[0]=='+'){
        index++
    }else if(arr[0]=='-'){
        flag=true
        index++
    }       
    var num=strToIntCore(arr,index,len,flag)
    return num;
}
function strToIntCore(arr,start,length,minus){
        var number=0;
        if(arr==null||start>=length) return number;
        while(start<length){
            if(arr[start]>='0'&& arr[start]<='9'){
                var flag=minus?-1:1;
                number=number*10+(arr[start]-'0')*flag
                start++
            }else{
                number=0;
                break;
            }
        }
        return number;
}
  • 表示数值的字符串

function isNumeric(s)
{
    // write code here

    return s.match(/[\+\-]?[0-9]*(\.[0-9]*)?([eE][\+-]?[0-9]+)?/g)[0]==s;
}
  • 赎金信

//给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,
判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。
如果可以构成,返回 true;否则返回 false//思路
将字符串 magazine 拆分成数组。
遍历 ransomNote,判断它每个字符串是否存在于 magazine 中。如果存在,则删掉 magazine 中的这个元素
(相当于杂志剪去这个字母,防止杂志中的字母不够用);如果不存在,表明 magazine 杂志的单词不够完成写赎金信。
var canConstruct = function (ransomNote, magazine) { magazine = magazine.split(''); for (let i = 0; i < ransomNote.length; i++) { if (magazine.indexOf(ransomNote[i]) != -1) { magazine.splice(magazine.indexOf(ransomNote[i]), 1) } else { return false; } } return true; };
  • 压缩字符串

//思路
给数组增加长度,然后给末尾添加 0。(为什么不直接从 0 开始,请思考对与否)。
遍历 chars 数组。
将长度赋值到数组中。

var compress = function (chars) {
  let j = 0,
    count = 1;
  chars.push(0);
  for (let i = 1; i < chars.length; i++) {
    if (chars[i] !== chars[i - 1]) {
      chars[j] = chars[i - 1];
      j++;
      if (count > 1) {
        let temp = count.toString();
        for (let k = 0; k < temp.length; k++) {
          chars[j++] = temp[k];
        }
      }
      count = 1;
    } else {
      count++;
    }
  }
  return j;
};

 

posted @ 2019-09-08 13:46  siyecaohhc  阅读(840)  评论(0编辑  收藏  举报