算法基础问题

本文探讨了JavaScript中实现算法的基础问题,包括查找字符串数组的最长公共前缀、统计字符串出现最多的字母、不借助临时变量交换整数以及99乘法表等。还涉及了数据结构如队列、栈、堆的概念和区别,以及基本类型和引用类型的对比。此外,文章还讲解了按值传递和按引用传递、二分查找和快速排序的实现,以及字符串字节长度的计算方法。最后,讨论了数组操作,如删除相邻重复项、数组打乱、组合排列、判断回文和统计重复字母等技巧。
摘要由CSDN通过智能技术生成

1. js实现查找字符串数组中的最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

输入: [“flower”,“flow”,“flight”] 输出: “fl”

思路:是很直观的方法,记录数组中第一个字符串的每个字符,依次判断数组中的每个字符串是否都有这个字符,使用every方法,如果返回false,说明该字符不是公共前缀了,不再判断。

  var longestCommonPrefix = function(arr) {
            if(arr.length) {//判断数组是否为空
                var res = ""; //记录公共前缀
                for(var i = 0; i < arr[0].length; i++) {
                    var temp = arr[0][i];
                    //每个字符串是否都有相同的字符
                    if(arr.every(el => {
                            return el.charAt(i) == temp;
                        })) {
                        res += temp; //记录公共前缀
                    } else break; //如果返回false,就停止判断,说明不是前缀了
                }
                return res;
            }
            return ""; //说明是空数组
        };

2. 统计一个字符串出现最多的字母

给出一段英文连续的英文字符窜,找出重复出现次数最多的字母

比如: 输入:afjghdfraaaasdenas 输出 : a

前面出现过去重的算法,这里需要是统计重复次数。

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;

}

module.exports = findMaxDuplicateChar;

3. 不借助临时变量,进行两个整数的交换

举例:输入 a = 2, b = 4 输出 a = 4, b =2

这种问题非常巧妙,需要大家跳出惯有的思维,利用 a , b进行置换。

主要是利用 + - 去进行运算,类似 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;

4. 99乘法表

for(var i=1;i<=9;i++){
for(var j=1;j<=i;j++){
document.write(i+"x"+j+"="+i*j+"&nbsp"+"&nbsp")
}
document.write("<br>")
}

删除字符串中的所有相邻重复项(JavaScript)

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例1: 输入:“abbaca” 输出:“ca” 解释: 例如,在 “abbaca” 中,我们可以删除 “bb”
由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa”
可以执行重复项删除操作,所以最后的字符串为 “ca”。

提示:

1 <= S.length <= 20000
S 仅由小写英文字母组成。
/**
 * @param {string} S
 * @return {string}
 */
var removeDuplicates = function(S) {

};

个人解法

var removeDuplicates = function(S) {
    S = S.split("");
    //对S中的字符两两进行判断
    for(var i = 0; i < S.length - 1; i++){
    	//如果相同
        if(S[i] === S[i + 1]){
        	//去掉这两个字符
            S.splice(i , 2);
            //如果这两个字符前面没有字符了
            if(i - 1 < 0){
            	//那么就重新判断这个位置的字符
                i--;
            }else{
            	//如果前面还有字符要判断前面的字符跟变化后的后面的字符是否相等
                i -= 2;
            }
        }
    }
    return S.join("");
};

分别简述队列、栈、堆的区别?

队列是先进先出:就像一条路,有一个入口和一个出口,先进去的就先出去。

栈是后进先出:就像摞盘子一样,摞在上边的先被使用。

堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。类似于钱包,可以用钱,用完了还回去。

  • 栈(Stack)是操作系统在建立某个进程时或者线程为这个线程建立的存储区域。在编程中,例如C/C++中,所有的局部变量都是从栈中分配内存空间,实际上也不是分配,只是从栈顶向上用就行,在退出函数的时候,只是修改栈指针就可以把栈中的内容销毁,所以速度最快。
  • 堆(Heap)是应用程序在运行的时候请求操作系统分配给自己的内存,一般是申请/给与的过程。由于从操作系统管理的内存分配所以在分配和销毁时都要占用时间,所以用堆的效率低的多!但是堆的好处是可以做的很大,C/C++对分配的Heap是不初始化的。

总结:栈(stack)会自动分配内存空间,会自动释放。堆(heap)动态分配的内存,大小不定也不会自动释放。

基本类型和引用类型(接2)

基本类型:简单的数据段,存放在栈内存中,占据固定大小的空间。包括undefined、string、boolean、null、Number

引用类型:指那些可能有多个值构成的对象,保存在堆内存中,包含引用类型的变量实际上保存的不是变量本身,而是指向该对象的指针。

按值传递和按引用传递

从一个向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终指向同一个对象。即复制的事栈中的地址而不是堆中的对象。

从一个变量复制另一个变量的基本类型的值,会创建这个值的副本。

用JavaScript实现二分法查找

二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤:

(1)首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。

(2)如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。

(3)如果某一步数组为空,则表示找不到目标元素。

二分查找:A为已按‘升序排列’的数组,x为要查询的元素 返回目标元素的下标

function binarySearch(A, x) {
    var low = 0,high = A.length -1
    while (low <= high) {
        var mid = Math.floor((low + high) / 2)
        
        if (x == A[mid]) {
            return mid
        }
        
        if (x < A[mid]) {
            high = mid -1
        } else {
            low = mid + 1
        }
    }
    return -1
}

用JavaScript实现数组快速排序

关于快排算法的详细说明,可以参考阮一峰老是的文章快速排序“快速排序”的思想很简单,整个排序过程只需要三步:

(1)在数据集之中,选择一个元素作为“基准”(pivot)

(2)所有小于“基准”的元素,都移动到“基准”的左边;所有大于“基准”的元素都移到“基准”的右边

(3)对“基准”左边和右边的两个子集,不断重复第一步和第二步,知道所有子集只剩下一个元素为止。

方法一(尽可能不用js数组方法):

function qSort(arr,low,high) {
    if(low < high) {
        var partKey = partition(arr,low,high)
        qSort(arr,low,partKey - 1)
        qSort(arr,partKey + 1, high)
    }
}
function partition(arr,low,high) {
    var key = arr[low]
    while(low < high) {
        while(low < high && arr[high] >= arr[key]) {
            high--
            arr[low] = arr[high]
        }
        
        while(low < high && arr[low] <= arr[key]) {
            low++
            arr[high] = arr[low]
        }
        arr[low] = key
        return low
    }
}

方法二(使用js数组方法):

function quickSort(arr) {
    if (arr.length <= 1) {
        return arr
    }
    var index = Math.floor(arr.length / 2)
    var key = arr.splice(index,1)[0]
    var left = [],right = []
    arr.forEach(function(v){
      v <= key ? left.push(v) : right.push(v)
    })
    return quickSort(left).concat([key],quickSort(right))
}

编写一个方法,求一个字符串的字节长度

假设:一个英文字符占用一个字节,一个中文字符占用两个字节

function GetBytes(str) {
    var len = str.length
    var bytes = len
    for (var i = 0; i < len; i++) {
        if (str.charCodeAt(i) > 255) {
            bytes++
        }
    }
    return bytes
}
alert(GetBytes('你好,world'))

找出下列正数组的最大差值

输入[20, 3, 11, 19, 16, 14]

function getMaxProfit(arr) {
   
   let max = Math.max.apply(null, a);//最大值
   let min = Math.min.apply(null, a);//最小值
   return max - min
}

如何消除一个数组里面重复的元素

基本数组去重(最好)

Array.prototype.unique = function() {
    var result = []
    this.forEach(function(v){
        if(result.indexOf(v) < 0) {
            result.push(v)
        }
    })
    return result
}

统计字符串中字母个数或统计最多字母数

输入: afijghiaaafsaadaes

输出: a

统计字符串中字母个数与统计最多字母数的思路相同

把每一道题的思路先写出来

思路:

统计字符串中字母个数:我们需要遍历所给的字符串,先拿一个字符放到新空间中,然后再拿第二个,与第一个进行比较,如果相同则丢弃,如果不相同则放到新数组中…,处理完则获得了字符串中的字母个数,将对应的长度返回即可。

统计最多字母数,并返回个数:比第一个多了个记录对应个数的操作。找到相同的元素时,将对于的元素的计数器加1.找到最大的计数,并返回。

function findMaxDuplicateChar(str) {
    if (str.length == 1) {
        return str
    }
    let charObj = {}
    for(let i = 0; i< str.length; i++) {
        if (!charOjb[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
}

链表与数组的区别(数据结构)

  • 数组
    数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素。,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少插入和删除元素,就应该用数组,
  • 链表
    链表中的元素是不存在顺序存储的,而是通过存在元素中的指针联系在一起,每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针。
    如果要访问链表中的一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表。

内存存储区别

数组从栈中分配空间,对于程序员方便快速,但自由度小。
链表从堆中分配空间,自由度大,但申请管理比较麻烦。

逻辑结构区别

数组必须实现定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
链表动态地进行存储分配,可以适应数据动态增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
总结

存取方式上,数组可以顺序存取或随机存取,而链表只能顺序存取;

存储位置上,数组逻辑上相邻的元素在物理逻辑上也相邻,而链表不一定;

存储空间上,链表由于带有指针域,存储密度不如数组大;

按序号查找时,数组可以随机访问,时间复杂度为o(1),而链表不支持随机访问,平均需要o(n);

按值查找时,若数组无序。数组和链表时间复杂度为o(n),但是当数组有序时,可以采用折半查找将时间复杂度降为o(logn);

插入和删除时,数组平均需要移动n/2个元素,而链表只需要修改指针即可;

空间分配方面: 数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败;

链表存储的节点空间值在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效;

经典数据结构涵盖了多种抽象数据类型(ADT),其中包括栈、队列、有序列表、排序表、哈希表及分散表、树、优先队列、集合和图等。对于每种情况,都可以选用数组或某一种链表数据结构来实现其抽象数据类型(ADT)。由于数组和链表几乎是建立所有ADT的基础,所以称数组与链表为基本数据结构。

将一个数组打乱

function shuffle (arr) {
    var len = arr.length, i = len, nTemp, iRandom;
    
    while (i--) {
        if (i !== (iRandom = Math.floor(Math.random()*len)) {
            
        }
    }
    
}

取出数组中所有排列组合(JS实现)

先定义一个函数,用于从 指定数组 取出 指定数量 的所有排列组合情况。
原理如下

先取第一项的所有情况,得到一个数组, 类似[ [1], [2], [3] ]
递归取出count - 1的所有情况,得到一个数组,类似 [ [a, b], [a,c], [a, d] ]
把第1步和第2步的两个数组交叉组合,即可得到最终结果

/**
 * 
 * @param {*} source 源数组
 * @param {*} count 要取出多少项
 * @param {*} isPermutation 是否使用排列的方式
 * @return {any[]} 所有排列组合,格式为 [ [1,2], [1,3]] ...
 */
function getNumbers(source, count, isPermutation = true) {
  //如果只取一位,返回数组中的所有项,例如 [ [1], [2], [3] ]
  let currentList = source.map((item) => [item]);
  if (count === 1) {
    return currentList;
  }
  let result = [];
  //取出第一项后,再取出后面count - 1 项的排列组合,并把第一项的所有可能(currentList)和 后面count-1项所有可能交叉组合
  for (let i = 0; i < currentList.length; i++) {
    let current = currentList[i];
    //如果是排列的方式,在取count-1时,源数组中排除当前项
    let children = [];
    if (isPermutation) {
      children = getNumbers(source.filter(item => item !== current[0]), count - 1, isPermutation);
    }
    //如果是组合的方法,在取count-1时,源数组只使用当前项之后的
    else {
      children = getNumbers(source.slice(i + 1), count - 1, isPermutation);
    }
    for (let child of children) {
      result.push([...current, ...child]);
    }
  }
  return result;
}

let arr = [1, 2, 3];

const result = getNumbers(arr, 2, false);
console.log(result);
//[ [ 1, 2 ], [ 1, 3 ], [ 2, 3 ] ]

const result2 = getNumbers(arr, 2);
console.log(result2);
//[ [ 1, 2 ], [ 1, 3 ], [ 2, 1 ], [ 2, 3 ], [ 3, 1 ], [ 3, 2 ] ]


关键点

如果是排列的方式,后续数组,只要排除当前项即可
getNumbers(source.filter(item => item !== current[0]), count - 1, isPermutation);
如果是组合方式,后续数组只能当前项之后的值
getNumbers(source.slice(i + 1), count - 1, isPermutation);

原因是:
当第一项为3时,
如果是排列,那么第二项允许为1;
如果是组合,则不允许为1——理由:当第一项为1时,已经有过1,3的组合,所以第一项为3,不允许出现3,1的组合

输出所有排列组合
循环数组长度,调用上面的函数即可

let arr = [1, 2, 3];

for (let i = 1; i <= arr.length; i++) {
  console.log(getNumbers(arr, i));
  console.log('\r\n');
}
console.log('\r\n--------\r\n');

for (let i = 1; i <= arr.length; i++) {
  console.log(getNumbers(arr, i, false));
  console.log('\r\n');
}


//输出结果如下
[ [ 1 ], [ 2 ], [ 3 ] ]

[ [ 1, 2 ], [ 1, 3 ], [ 2, 1 ], [ 2, 3 ], [ 3, 1 ], [ 3, 2 ] ]

[ [ 1, 2, 3 ],
  [ 1, 3, 2 ],
  [ 2, 1, 3 ],
  [ 2, 3, 1 ],
  [ 3, 1, 2 ],
  [ 3, 2, 1 ] ]

--------

[ [ 1 ], [ 2 ], [ 3 ] ]

[ [ 1, 2 ], [ 1, 3 ], [ 2, 3 ] ]

[ [ 1, 2, 3 ] ]

判断一个单词是否是回文

回文是指把相同的词汇或句子,在下文中调换位置或颠倒过来,产生首尾回环的情趣,叫做回文,也叫回环。比如 mamam redivider .

容易想到用for 将字符串颠倒字母顺序然后匹配就行了。其实重要的考察的就是对于reverse的实现。其实我们可以利用现成的函数,将字符串转换成数组,这个思路很重要,我们可以拥有更多的自由度去进行字符串的一些操作。

function checkPalindrom(str) {  
    return str == str.split('').reverse().join(''); }

统计一个字符串出现最多的字母

给出一段英文连续的英文字符窜,找出重复出现次数最多的字母

输入 : afjghdfraaaasdenas 输出 : a

前面出现过去重的算法,这里需要是统计重复次数。

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; } module.exports = findMaxDuplicateChar; 

随机生成指定长度的字符串

比如给定 长度 8 输出 4ldkfg9j

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; } module.exports = randomString; 
https://blog.csdn.net/weixin_38984353/article/details/80393412?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161649317216780274187214%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=161649317216780274187214&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-2-80393412.pc_search_result_cache&utm_term=%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%E7%AE%97%E6%B3%95
https://blog.csdn.net/weixin_39598796/article/details/110815722?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&dist_request_id=1328690.21502.16166553268500657&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值