碎碎念:
亲爱的读者:你好!我的名字叫昌龙 【Changlon】 —— 一个非科班程序员、一个致力于前端的开发者、一个热爱生活且又时有忧郁的思考者。
如果我的文章能给你带来一些收获,你的点赞收藏将是对我莫大的鼓励!
我的邮箱:thinker_changlon@163.com
我的Github: https://github.com/Changlon
无论从事前端后端,不得不说算法才是决定一个程序员水平高低,工资收入的基石。算法能力也是一个程序员未来能走多远的一个决定性因素。小龙这里为大家总结了最新大厂面试题锦,为你的面试推波助力。
程序员之进大厂必刷算法题系列
数组
字符串
链表
栈
哈希
二叉树
二分算法
分治算法(快排序)
动态规划
深度优先,广度优先算法
递归,回溯算法
贪心算法
文章目录
一、NC141 判断回文
描述
给定一个字符串,请编写一个函数判断该字符串是否回文。如果回文请返回true,否则返回false。
示例1
输入: “absba”
返回值: true
示例2
输入: “ranko”
返回值: false
思路: 判断是否是回文字符串,就是从字符串的左边遍历和从右边遍历的效果是一样的。可以用双指针分别从前向后,和从右向前遍历,加入遇到不一样的字符就说明不是一个回文字符串。
function judge( str ) {
let left = 0,right = str.length-1
while(left<=right) {
if(str[left]!=str[right]) return false
left++,right--
}
return true
}
二、NC31 第一个只出现一次的字符
描述
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回
-1(需要区分大小写).(从0开始计数)
示例1
输入: “google”
返回值: 4
思路:维护一个map,先遍历一遍字符串,将每个字符以及其出现的次数存入map,接着再次遍历一遍字符串,查询每次遍历的字符出现的次数,如果次数为1则返回。没有返回-1
function FirstNotRepeatingChar(str){
const map = new Map()
for(let char of str) {
if(!map.get(char)) {
map.set(char,1)
}else{
map.set(char,map.get(char)+1)
}
}
for(let i =0;i<str.length;++i) {
const char = str[i]
if(map.get(char)==1) {
return i
}
}
return -1
}
三、NC55 最长公共前缀
描述 编写一个函数来查找字符串数组中的最长公共前缀。
示例1
输入: [“abca”,“abc”,“abca”,“abc”,“abcc”]
返回值: “abc”
思路:
- 将字符串数组按照字典集排序;
- 将第一个字符与最后一个进行比较。
function longestCommonPrefix(strs) {
if(!strs||strs.length<=0) return ""
if(strs.length==1) return strs[0]
strs.sort()
let len = strs.length
let l = Math.min(strs[0].length,strs[len-1].length)
let maxStr = ""
for(let i=0;i<l;i++) {
for(let j= i+1;j<l+1;j++) {
let subStr = strs[0].substring(i,j)
if(strs[len-1].indexOf(subStr)>=0) {
maxStr = maxStr.length>subStr.length?maxStr:subStr
}
}
}
return maxStr
}
四、NC103 反转字符串
描述 写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000)
示例1
输入: “abcd”
返回值: “dcba”
思路1: 倒着遍历目标字符串,每次将字符赋给一个变量,最后就是反转的效果。
时间复杂度: O(n)
function solve( str ) {
let ret = ""
for(let i =str.length-1;i>=0;i--) {
ret = ret + str[i]
}
return ret
}
思路2:双指针
时间复杂度: O(n/2)
function solve_( str ) {
//由于js语言不能操作地址,所以先将str转为数组形式
let strs = str.split('')
let i =0,j = strs.length-1
while(i<j) {
let t = strs[i]
strs[i] = strs[j]
strs[j] = t
i++
j--
}
return strs.join("")
}
五、NC113 验证IP地址
描述 编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址
IPv4 地址由十进制数和点来表示,每个地址包含4个十进制数,其范围为 0 - 255, 用(".")分割。比如,172.16.254.1;
同时,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01 是不合法的。IPv6 地址由8组16进制的数字来表示,每组表示 16 比特。这些组数字通过 (":")分割。比如,
2001:0db8:85a3:0000:0000:8a2e:0370:7334 是一个有效的地址。而且,我们可以加入一些以 0
开头的数字,字母可以使用大写,也可以是小写。所以, 2001:db8:85a3:0:0:8A2E:0370:7334 也是一个有效的
IPv6 address地址 (即,忽略 0 开头,忽略大小写)。然而,我们不能因为某个组的值为 0,而使用一个空的组,以至于出现 (:😃 的情况。 比如,
2001:0db8:85a3::8A2E:0370:7334 是无效的 IPv6 地址。 同时,在 IPv6 地址中,多余的 0
也是不被允许的。比如, 02001:0db8:85a3:0000:0000:8a2e:0370:7334 是无效的。说明: 你可以认为给定的字符串里没有空格或者其他特殊字符。
示例1
输入: “172.16.254.1”
返回值: “IPv4”
说明: 这是一个有效的 IPv4 地址, 所以返回 “IPv4”
示例2
输入: “2001:0db8:85a3:0:0:8A2E:0370:7334”
返回值: “IPv6”
说明: 这是一个有效的 IPv6 地址, 所以返回 “IPv6”
示例3
输入: “256.256.256.256”
返回值: “Neither”
说明: 这个地址既不是 IPv4 也不是 IPv6 地址
备注:
ip地址的类型,可能为 IPv4, IPv6, Neither
这道题其实没啥可说的,就是用正则匹配两种Ip地址格式
function solve( IP ) {
// write code here
const IPv4Reg = /^(25[0-5]|2[0-4]\d|(0|1[0-9]{0,2}))(\.(25[0-5]|2[0-4]\d|(0|1[0-9]{0,2}))){3}$/
const IPv6Reg = /^([\da-fA-F]{1,4}:){6}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^::([\da-fA-F]{1,4}:){0,4}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:):([\da-fA-F]{1,4}:){0,3}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){2}:([\da-fA-F]{1,4}:){0,2}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){3}:([\da-fA-F]{1,4}:){0,1}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){4}:((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}$|^:((:[\da-fA-F]{1,4}){1,6}|:)$|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)$|^([\da-fA-F]{1,4}:){2}((:[\da-fA-F]{1,4}){1,4}|:)$|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)$|^([\da-fA-F]{1,4}:){4}((:[\da-fA-F]{1,4}){1,2}|:)$|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?$|^([\da-fA-F]{1,4}:){6}:$/
if(IPv4Reg.test(IP)){
return "IPv4"
}else if(IPv6Reg.test(IP)) {
return "IPv6"
}else{
return "Neither"
}
}
六、NC17 最长回文子串
描述 对于一个字符串,请设计一个高效算法,计算其中最长回文子串的长度。
给定字符串A以及它的长度n,请返回最长回文子串的长度。
示例1
输入: “abc1234321ab”,12
返回值: 7
思路: 动态规划
参考文章: https://blog.nowcoder.net/n/99462149a22d479ba9a3d5c8e7fd0247?f=comment
我的思路:
上面文章中的思路大概就是定义一个二维数组dp[i][j]来表示字符串A[i:j]是否是一个回文字符串。然后判断A[i:j]是否是回文的条件是A[i+1:j-1]是否是一个回文并且A[i]==A[j]。然后通过计算下标统计最大回文字符串长度。
而我的dp数组表示的则是字符串A[i:j]中回文字符串的长度,通过一个maxLen变量来记录最大值。
function getLongestPalindrome(A,n) {
let dp = []
let max = 0
//js初始化二维数组
for(let i =0;i<n;++i){
dp[i] = []
for(let j=0;j<n;++j) {
dp[i][j] = 0
}
}
for(let i =n-1;i>=0;--i) {
for(let j =i;j<n;++j) {
if(A[i]==A[j]) {
if(j-i<2) {
//对于单个字符 如a
//或者两个字符 如 aa 的情况
dp[i][j] = j-i+1
}else{
//dp[i+1][j-1]==(j-i-1)
//判断的是字符A[i+1:j-1]是否是一个回文字符串,如果是回文字符串那dp[i+1][j-1]的值应该和字符串的长度相等
dp[i][j] = dp[i+1][j-1]==(j-i-1)? dp[i+1][j-1]+2:dp[i+1][j-1]
}
}else{
dp[i][j] = dp[i+1][j-1]
}
max = Math.max(max,dp[i][j])
}
}
return max
}
七、NC1 大数加法
描述 以字符串的形式读入两个数字,编写一个函数计算它们的和,以字符串形式返回。
(字符串长度不大于100000,保证字符串仅由’0’~'9’这10种字符组成)
示例1 输入: “1”,“99”
返回值: “100”
说明: 1+99=100
function solve( s , t ) {
let res = ""
let carry = 0
let l1 = s.length,l2 = t.length
while(l1>0&&l2>0) {
let up = Number.parseInt(s[l1-1])
let down = Number.parseInt(t[l2-1])
let result = (up+down+carry)%10 //上下两位数相加的结果
carry = (up+down+carry)>=10?1:0 //上下两个数相加的进位
res = result + res
l1--,l2--
}
let c = s.length>t.length?l1:l2
let str = s.length>t.length?s:t
if(carry==0) {
return str.substring(0,c) + res
}else if(carry==1&&!l1&&!l2) {
return "1" + res
}
return solve(str.substring(0,c),"1") + res
}
八、NC100 将字符串转化为整数
描述 实现函数 atoi 。函数的功能为将字符串转化为整数
提示:仔细思考所有可能的输入情况。这个问题没有给出输入的限制,你需要自己考虑所有可能的情况。
function atoi( str ) {
const INT_MAX = 2147483647
const INT_MIN = -2147483648
str = str.trim()
const numberReg = /[0-9]/
let ret = 0
let oper = 1
while(str[0]=='0'&&str.length>1)
str = str.substring(1,str.length)
for(let i =0;i<str.length;i++) {
if(numberReg.test(str[i])) {
let c = Number.parseInt(str[i])
ret = ret*10 +c
}else{
if(str[i]=='-') oper = -1
else if(str[i]=='+') oper = 1
else break
}
}
if(ret*oper>INT_MAX) return INT_MAX
if(ret*oper<INT_MIN) return INT_MIN
return ret*oper
}
九、NC149 kmp算法
描述 给你一个文本串S,一个非空模板串T,问S在T中出现了多少次
示例1
输入: “ababab”,“abababab” 返回值: 2
示例2
输入: “abab”,“abacabab” 返回值: 1
备注:
空间O(n)时间O(n)的算法
function kmp( S , T ) {
let ret = 0
let next = []
let ni = 1,nj= 0
let k = 0
next[nj] = 0
//求解next数组
while(ni<S.length) {
if(S[nj]==S[ni]) {
k++
next[ni] = k
ni++,nj=k
}else{
if(nj!=0) {
nj = next[nj-1]
k = next[j]
}else{
k = 0
next[ni] =k
ni++
}
}
}
let i = 0,j=0
for(;i<T.length;i++) {
while(j>0&&T[i]!=S[j])
j = next[j-1]
if(T[i]==S[j])
j++
if(j == S.length){
ret++
j = next[j-1]
}
}
return ret
}
十、NC28 最小覆盖子串
描述 给出两个字符串 S 和 T,要求在O(n)的时间复杂度内在 S 中找出最短的包含 T中所有字符的子串。 例如:
S =“XDOYEZODEYXNZ”
T =“XYZ”
找出的最短子串为"YXNZ".注意: 如果 SS 中没有包含 TT 中所有字符的子串,返回空字符串 “”;
满足条件的子串可能有很多,但是题目保证满足条件的最短的子串唯一。
示例1
输入: “XDOYEZODEYXNZ”,“XYZ”
返回值: “YXNZ”
思路: 滑动窗口算法
- begin开始指向0, end一直后移,直到begin - end区间包含T中所有字符。
记录窗口长度d - 然后begin开始后移移除元素,直到移除的字符是T中的字符则停止,此时T中有一个字符没被
包含在窗口, - 继续后移end,直到T中的所有字符被包含在窗口,重新记录最小的窗口d。
- 如此循环知道end到S中的最后一个字符。
function minWindow( S , T ) {
if(S==T) return S
let window = [],target = []
//记录T中个字符出现的次数
for(let i =0;i<T.length;++i) {
if(!target[T[i]]) {
target[T[i]] = 1
}else{
target[T[i]]++
}
}
for(let i =0;i<S.length;++i) {
window[S[i]] = 0
}
const MAX_LEN = 999999999
let left =0,right =0;
let start = 0, minLen = MAX_LEN
let count = 0
while(right<S.length) {
let c = S[right]
++right
if(target[c]){
window[c]++
if(window[c]<=target[c]) count++
}
//当窗口中包含了T的全部字符
while(count==T.length) {
if(right-left<minLen) {
start = left
minLen = right - left
}
c = S[left]
++left
if(target[c]) {
if(window[c]<=target[c]) count--
--window[c]
}
}
}
return minLen==MAX_LEN ?"": S.substr(start,minLen)
}
十 一、NC142 最长重复子串
描述
定义重复字符串是由两个相同的字符串首尾拼接而成,例如 “abcabc"便是长度为6的一个重复字符串,而"abcba”
则不存在重复字符串。给定一个字符串,请返回其最长重复子串的长度。
若不存在任何重复字符子串,则返回 0 。
示例1
输入: “ababc”
返回值: 4
说明:
abab为最长的重复字符子串,长度为4
示例2
输入: “abcab”
返回值: 0
说明:
该字符串没有重复字符子串 备注: 字符串长度不超过10000,且仅由小写字母组成
思路:
以 "ababc"为例,长度为5,这样如果这个字符串中存在重复字符串,就求出其中一个重复字符串的长度为2,我们要做的就是从字符串头开始两两比对,如果发现两个一样就返回总长度,不一样则继续比对下去。
function solve( a ) {
if(a==null||a.length<=1) return 0
let maxLen = Number.parseInt(a.length/2)
while(maxLen>0) {
for(let i = 0;i<=a.length-maxLen*2;++i) {
if(a.slice(i,i+maxLen)===a.slice(i+maxLen,i+maxLen*2)) {
return maxLen*2
}
}
maxLen--
}
return 0
}
OK,本算法系列的字符串部分就精选了这十几道题,有易有难。如果你看完之后觉得不错不要忘了给我评论点赞哦!我一定会继续认真做好后面的章节!
由于本人技术水平有限,博客中难免出现的一些错误,有纰漏之处恳请各位大佬不吝赐教!