1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | |
1 |
面试题4:二维数组中的查找
题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:通过将target与数组右上角的数组进行判断来逐步缩小查询数组的范围。
function Find(target, array)
{
const n = array.length,
m = array[0].length;
let row = 0,
col = m-1;
while(row < n && col >= 0){
if(target < array[row][col]){
col--;
}else if(target > array[row][col]){
row++;
}else{
return true
}
}
return false;
}
//牛客报错
function Find(target, array)
{
let res = false;
let i = array.length,
j = array[0].length;
let row = 0,
col = j-1;
while(row < i && col >= 0){
while((target < array[row][col]) && (col >= 0)){
col--;
}
while((target > array[row][col]) && (row < i)){
row++;
}
if(target === array[row][col]){res = true;break;}
}
return res;
}
面试题5:替换空格
题目描述
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路:用正则式
function replaceSpace(str)
{
return str.replace(/\s/g,'%20');
}
面试题6:从尾到头打印链表
题目描述
输入一个链表,按链表从尾到头反过来打印出每个节点的值。
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function printListFromTailToHead(head)
{
// 使用push和reverse
let res = [];
let pNode = head;
while(pNode != null){
res.push(pNode.val);
pNode = pNode.next;
}
return res.reverse();
//使用unshift
let res = [];
let pNode = head;
while(pNode != null){
res.unshift(pNode.val);
pNode = pNode.next;
}
return res;
}
面试题7:重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。(构建一棵树)
思路
一直抓住数组pre和vin就好了
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function reConstructBinaryTree(pre, vin)
{
// write code here
if (pre.length === 0 || vin.length === 0) {
return null;
}
// 前序第一个是根节点,也是中序左右子树的分割点
const index = vin.indexOf(pre[0]),
// left,right对应的是数组vin的左子树和右子树
left = vin.slice(0, index),
right = vin.slice(index + 1),
rootVal = pre[0],
node = new TreeNode(rootVal);
node.left = reConstructBinaryTree(pre.slice(1, index + 1), left);
node.right = reConstructBinaryTree(pre.slice(index + 1), right);
return node;
}
// 方法二:返回的是一个对象。对象就是一个节点啊!神奇吧!
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function reConstructBinaryTree(pre, vin) {
// write code here
if (pre.length === 0 || vin.length === 0) {
return null;
}
// 前序第一个是根节点,也是中序左右子树的分割点
const index = vin.indexOf(pre[0]),
left = vin.slice(0, index),
right = vin.slice(index + 1);
return {
val: pre[0],
// 递归左右子树的前序、中序
left: reConstructBinaryTree(pre.slice(1, index + 1), left),
right: reConstructBinaryTree(pre.slice(index + 1), right)
};
}
面试题9:用两个栈实现队列
题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
思路(来源)
栈的特性:先入后出,队列的特性:先入先出。
算法分为入队和出队过程。
入队过程:将元素放入 inStack 中。
出队过程:
- outStack 不为空:弹出元素(巧妙地方在于就算此时又有新的数字传入到inStack,但将要弹出的不会是这个先进的数字,而是先入的,先入的在outStack里。)
- outStack 为空:将 inStack 元素依次弹出,放入到 outStack 中(在数据转移过程中,顺序已经从后入先出变成了先入先出)
const outStack = [],
inStack = [];
function push(node) {
inStack.push(node);
}
function pop() {
if (!outStack.length) {// 有点巧妙。
while (inStack.length) {
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
拓展:用两个队列实现一个栈
思路(来源)
栈的特性:先入后出,队列的特性:先入先出。
准备两个队列q1
和q2
。算法过程分为入栈和出栈。
入栈过程:
- q1 为空,放入 q2
- q2 为空,放入 q1
- 均为空,默认放入 q1
出栈过程:
- q1 为空:
- 依次取出 q2 中的元素(除了最后一个),并且放入 q1 中
- 取出 q2 中的最后一个元素,返回结果
- q2 为空:
- 依次取出 q1 中的元素(除了最后一个),并且放入 q2 中
- 取出 q1 中的最后一个元素,返回结果
面试题10:斐波那契数列
题目描述
要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。n<=39
思路
方法一:递归;方法二:循环,从下到上
function Fibonacci(n)
{
var one = 0,
two = 1;
//写法一
//if(n === 0)return 0;
//写法二
var result = [0,1];
if(n<2)return result[n];
for(let i = 2; i <= n; i++){
let temp = one + two;
one = two;
two = temp;
}
return two;
}
拓展1:跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
思路
级数 | 跳法 |
1级 | 1种 |
2级 | 2种:1,2 |
3级 | 3种:1+1+1, 1+2, 2+1 |
4级 | 5种:1+1+1+1, 1+1+2, 1+2+1, 2+1+1, 2+2 |
摘自《剑指Offer》P78:我们把n级台阶时的跳法看成n的函数,记为f(n)。当n>2时,第一次跳的时候就有两种不同的选择:一是第一次只跳1级,此时跳法数目等于后面剩下的n-1级台阶的跳法数目,即为f(n-1);二是第一次跳2级,此时跳法数目等于后面剩下的n-2级台阶的跳法数目,即为f(n-2)。因此,n级台阶的不同跳法的总数f(n)=f(n-1)+f(n-2)。
function jumpFloor(number)
{
//递归
/*if(number <= 1)return 1;
else if(number === 2)return 2;
else return jumpFloor(number-1) + jumpFloor(number-2);*/
//循环
var result = [1,2];
if(number < 3)return result[number-1];
var one = 1,
two = 2;
for(let i = 3; i <=number; i++){//如果易懂一点就像上一个代码新增一个变量。
two = one + two;
one = two - one;
}
return two;
}
拓展2:变态跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路(来源)
根据上一个题目可以知道,青蛙只跳1或2可以得出是一个斐波那契问题,即a[n]=a[n-1]+a[n-2],那么能跳1,2,3个台阶时a[n]=a[n-1]+a[n-2]+a[n-3],......
那么有:
a[n]=a[n-1]+a[n-2]+......+a[1];..........................①
a[n-1]= a[n-2]+......+a[1];..........................②
两式相减可知:a[n]=2*a[n-1];
所以其实是一个等比数列。f(n) =
function jumpFloorII(number)
{
var result = 1;
if(number === 0)return 0;
while(--number){
result *= 2;
}
return result;
}
拓展3:矩形覆盖
题目描述
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
思路(来源)
注意:比如当上面出现了横着放,那对应的下面其实已经确定了,只能横着放,不会有其他的放法。
n个2*1的矩形 | 方法 |
1个 | 1种 |
2个 | 2种:竖着放,横着放 |
3个 | 3种:竖上横(下横),竖竖竖,上横(下横)竖 |
4个 | 5种:1+1+1+1, 1+1+2, 1+2+1, 2+1+1, 2+2 |
面试题11:旋转数组的最小数字
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
思路
Step1:用两个指针(i,j)分别指向数组的第一个元素(第一个指针i)和最后一个元素(第二个指针j)。判断大小,两种情况:
- 第一个元素小于最后一个元素:说明旋转数组等于原数组本身,如{1,2,3,4,5}是{1,2,3,4,5}的一个旋转。此时最小值为第一个元素(可以直接return)
- 第一个元素大于等于最后一个元素,进入Step2
Step2:找到数组中间元素Arr[mid],将中间元素与第一个元素和最后一个元素比较。有三种情况:
- Arr[mid]大于等于Arr[i]:说明中间元素处于前一个递增子数组。最小值会在 mid~j 这部分,因此:i = mid,缩小寻找范围
- Arr[mid]小于等于Arr[j]:说明中间元素处于后一个递增子数组。最小值会在 i~mid 这部分,因此:j = mid,缩小寻找范围
- (特例)当Arr[i] == Arr[mid] == Arr[j],无法判断中间的数字是位于前面的子数组还是后面的子数组,因此要采用顺序查找的方法来实现
Step3:因为第一个指针总是指向前面递增数组的元素,第二个指针总是指向后面递增数组的元素。最终第一个指针将指向前面子数组的最后一个元素,而第二个指针将指向后面子数组的第一个元素。因此,循环结束条件为这两个指针的距离为1。
function minNumberInRotateArray(rotateArray)
{
var len = rotateArray.length;
if(len === 0)return 0;
var i = 0,
j = len-1;
while(j - i > 1){
let middle = Math.floor((i+j)/2);
if(rotateArray[i] === rotateArray[j] && rotateArray[i] === rotateArray[middle]){//三个数相同时
let min = a[i];
for(let k = 0; k < len; k++){
if(rotateArray[k] < min)min = rotateArray[k];
}
return min;
}else if(rotateArray[i] < rotateArray[j]){
return rotateArray[i];
}else{
if(rotateArray[i] <= rotateArray[middle]){
i = middle;
}else if(rotateArray[middle] <= rotateArray[j]){
j = middle;
}
}
}
return rotateArray[j];
}
面试题15:二进制中1的个数
题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
思路
书P101-102,https://www.cnblogs.com/wuguanglin/p/NumberOf1.html
常用技巧:
- 把一个整数减去1之后再和原来的整数做与运算,得到的结果相当于把整数的二进制表示中最右边的1变成0。
- 把整数右移一位和把整数除以2在数学上是等价的,但因为除法的效率比移位运算要低得多,在实际编程中尽可能地用移位运算符替代乘除法。
function NumberOf1(n)
{
//方法一(居然不用先将整数转换成二进制!
/*var flag = 1,
count= 0;
while(flag){
if(n & flag){
count++;
}
flag = flag << 1;
}
return count;*/
//方法二
var count = 0;
while(n){
count++;
n = n & n-1;
}
return count;
}
面试题16:数值的整数次方
题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0
思路(两个方法)
方法一:
n & 0x1 = 0:n为偶数, n & 0x1 = 1:n为奇数
function Power(base, exponent)
{
var result = 1;
if(exponent < 0){
if(base !== 0) PowerWithExponent(base, -exponent);
else throw new Error('分母不能为0');
}else if(base == 0){
return 0;
}else{
PowerWithExponent(base, exponent);
}
function PowerWithExponent(base, exponent){
if(exponent == 0)return 1;
if(exponent == 1)return base;
result = PowerWithExponent(base, exponent>>1);//用右移运算符代替除以2
result *= result;
if(exponent & 0x1 == 1){//判断是奇数还是偶数
result *= base;
}
}
return (exponent > 0 ? result : (1/result));
}
方法二:快速求幂 参考
首先要明白:
,然后:
,,
function Power(base, exponent)
{
var result = 1,
n;
if(exponent > 0)n = exponent;
else{//exponent <= 0
if(base == 0)throw new Error('分母不能为0');
else n = -exponent;
}
while(n){
if(n & 0x1){
result *= base;
}
base *= base;
n = n >> 1;
}
return exponent > 0 ? result : (1/result);
}
面试题18:删除链表中重复的节点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路
注意几点:①要考虑头结点可能与后面的节点重复,即头结点也有可能被删除,因此要建一个新的头结点
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function deleteDuplication(pHead)
{
if(pHead === null || pHead.next === null){
return pHead;
}
const head = new ListNode('x');
head.next = pHead;
let pre = head,
cur = head.next;
while(cur !== null){
//注意!涉及到cur.next的值需要提前判断cur.next是否为空,否则会出错。
//因为如果cur是尾节点,此时cur不为空,而cur.next为空,那cur.next的val值是未知的。
if(cur.next !== null && cur.val === cur.next.val){
while(cur.next !== null && cur.val === cur.next.val){
cur = cur.next;
}
pre.next = cur.next;
cur = cur.next;
}else{
pre = pre.next;
cur = cur.next;
}
}
return head.next;
}
面试题20:表示数值的字符串
题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
思路(原博)
运用js的正则式
//s字符串
function isNumeric(s)
{
//方法一,search()返回的是正则表达式在字符串中首次匹配项的索引。这里不可以把^$去掉。
//return s.search(/^[+-]?\d*(\.\d*)?$/) === 0 ||
//s.search(/^[+-]?\d+(\.\d*)?[Ee][+-]?\d+$/) === 0;
//方法二,match()返回的是一个数组。有点疑问,这个表达式内不可以同时加^$,否则牛客网会直接报错
return s.match(/[+-]?\d*(\.\d+)?([Ee][+-]?\d+)?/g)[0] === s;
//return s.search(/[+-]?\d*(\.\d+)?([Ee][+-]?\d+)?/ === 0); 这个是错的。比如12e会被识别为true,但正确答案是false
//因为没有开始结尾符号,导致匹配了12就完事了,不会看e后面是否满足。
}
正则式
在 JavaScript中,正则表达式也是对象。这些模式被用于
RegExp
的exec
和test
方法, 以及String
的match
、matchAll
、replace
、search
和split
方法。
match、search的区别:match返回的是一个数组,search返回的是正则表达式在字符串中首次匹配项的索引
// match()
var str = 'For more information, see Chapter 3.4.5.1';
var re = /see (chapter \d+(\.\d)*)/i;
var found = str.match(re);
console.log(found);
// logs [ 'see Chapter 3.4.5.1',
// 'Chapter 3.4.5.1',
// '.1',
// index: 22,
// input: 'For more information, see Chapter 3.4.5.1' ]
// 'see Chapter 3.4.5.1' 是整个匹配。
// 'Chapter 3.4.5.1' 被'(chapter \d+(\.\d)*)'捕获。
// '.1' 是被'(\.\d)'捕获的最后一个值。
// 'index' 属性(22) 是整个匹配从零开始的索引。
// 'input' 属性是被解析的原始字符串。
_______________________________________________________
//search
var str = "hey JudE";
var re = /[A-Z]/g;
var re2 = /[.]/g;
console.log(str.search(re)); // returns 4, which is the index of the first capital letter "J"
console.log(str.search(re2)); // returns -1 cannot find '.' dot punctuation
^$的重要性
如果使用了^和$,意味着输入的整个字符串都要用来和正则式匹配。如果不使用^和$,对于\d{5,12}而言,这个正则式只能保证字符串里包含5到12连续位数字,而使用^和$以为着整个字符串只能5到12位数字。上几张图
面试题21:调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路
相对位置不变可以联想到稳定性,而冒泡具有稳定性,所以可以模拟冒泡实现。(方法2)
方法1和方法3都是用空间换时间,但方法3更巧妙。方法3:原博
有一个坑:位与运算符 和 等号 的优先级。等号 高于 位于运算符。如果我们利用位与运算符写一个判断array[j]是偶数的表达式为:array[j] & 0x1 === 0,则先进行右边0x1 === 0的判断,再进行位与运算,这整个表达式将一直是false,并不能判断是否为偶数。正确的写法是加一个括号:(array[j] & 0x1) === 0
// 方法1:最不好的代码
function reOrderArray(array)
{
var odd = [],
even = [];
var len = array.length,
i = 0;
while(i < len){
if((array[i] & 0x1) === 1)odd.push(array[i++]);
else even.push(array[i++]);
}
return odd.concat(even);
}
// 方法2
function reOrderArray(array)
{
var flag,
len = array.length;
for(var i = 0; i < len; i++){
flag = 0;
for(var j = 0; j < len-1; j++){//注意,这里和冒泡有点不同。j和i都是从0开始
if((array[j] & 0x1) === 0 && (array[j+1] & 0x1) === 1){
[array[j],array[j+1]] = [array[j+1],array[j]];
flag = 1;
}
}
if(flag == 0)break;//全程无交换
}
return array;
}
//方法3
function reOrderArray(array) {
// oddBegin主要是用作奇数的索引,oddCount实际上是计算奇数个数,但可用作偶数的开头索引
// newArray用来存储,以空间换时间,复杂度为O(n)
let oddBegin = 0,
oddCount = 0;
const newArray = [];
for (let i = 0; i < array.length; i++) {
if (array[i] & 1) {
oddCount++;
}
}
for (let i = 0; i < array.length; i++) {
if (array[i] & 1) {
newArray[oddBegin++] = array[i];
} else {
newArray[oddCount++] = array[i];
}
}
return newArray;
}
//不要求保持相对位置相同的话,这个也可以。
function reOrderArray(array)
{
var len = array.length,
i = 0,//奇数
j = len-1;//偶数
while(j >= i){
while((array[i] & 0x1) === 1)i++;
while((array[j] & 0x1) === 0)j--;
if(i <= j){
[array[i],array[j]] = [array[j],array[i]];
i++;
j--;
}
}
return array;
}
面试题22:链表中倒数第k个节点
题目描述
输入一个链表,输出该链表中倒数第k个结点。
思路
双指针。比如我要求倒数第3的节点,倒数第3是倒数第1(倒数第1相当于尾节点)的前两个节点,相差2,所以从头开始算时,指向倒数第3的指针要比指向尾节点的指针迟2个进场。
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function FindKthToTail(head, k)
{
if(head == null || k <= 0)return null;
var p1 = head,
p2;
while(--k){// 注意这里是--k,不是k--。比如当k=1时
if(p1.next != null){
p1 = p1.next;
}else{
return null;
}
}
p2 = head;
while(p1.next != null){
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
// 自己写的,写的很难看。为了通过而通过。主要是自己想的不够彻底
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function FindKthToTail(head, k)
{
var p1, p2,
n = 0;//节点数
p1 = head;
p2 = null;
if(head == null || k <= 0)return null;
while(p1.next !== null){
n++;
if(n === k)p2 = head;
if(p2 !== null)p2 = p2.next;
p1 = p1.next;
}
n = n + 1;//指针指向尾节点时节点数是n-1,因此退出循环后要+1
if(n < k)return null;
else if(n == k)return head;
else return p2;
}
面试题24:反转链表
题目描述
输入一个链表,反转链表后,输出新链表的表头。
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function ReverseList(pHead)
{
if(pHead === null)return null;
let pNode, pPrev, res;
pNode = pHead;
pPrev = null;
while(pNode != null){
let pNext = pNode.next;
pNode.next = pPrev;
if(pNext == null)res = pNode;
pPrev = pNode;
pNode = pNext;
}
return res;
}
面试题25:合并两个排序的链表
题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
// 非递归,两个指针
function Merge(pHead1, pHead2)
{
let pMergeHead = new ListNode(null),
p = pMergeHead;
while(pHead1 != null && pHead2 != null){
if(pHead1.val > pHead2.val){
p.next = pHead2;
pHead2 = pHead2.next;
}else{
p.next = pHead1;
pHead1 = pHead1.next;
}
p = p.next;
}
if(pHead1 == null)p.next = pHead2;
else p.next = pHead1;
return pMergeHead.next;
}
// 递归
function Merge(pHead1, pHead2)
{
if(pHead1 == null)return pHead2;
if(pHead2 == null)return pHead1;
let pMergeHead = null;
if(pHead1.val < pHead2.val){
pMergeHead = pHead1;
pMergeHead.next = Merge(pHead1.next, pHead2)
}else{
pMergeHead = pHead2;
pMergeHead.next = Merge(pHead1, pHead2.next)
}
return pMergeHead;
}
面试题26:树的子结构
题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function HasSubtree(pRoot1, pRoot2)
{
if(pRoot1 == null || pRoot2 == null)return false; //一开始把||写成了&&,牛客会报错
let res = false;
if(pRoot1.val === pRoot2.val)res = doesTree1HasTree2(pRoot1, pRoot2);
if(!res)res = HasSubtree(pRoot1.left, pRoot2);
if(!res)res = HasSubtree(pRoot1.right, pRoot2);
return res;
}
function doesTree1HasTree2(pRoot1, pRoot2){
if(pRoot2 == null)return true;
if(pRoot1 == null)return false;
if(pRoot1.val !== pRoot2.val)return false;
return doesTree1HasTree2(pRoot1.left, pRoot2.left) && doesTree1HasTree2(pRoot1.right, pRoot2.right)
}
面试题27:二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function Mirror(root)
{
if(root == null)return;
Mirror(root.left);
Mirror(root.right);
[root.left, root.right]=[root.right, root.left];
return root;
}
来源:CSDN
作者:和樱
链接:https://blog.csdn.net/weixin_40836227/article/details/103274886