牛客的算法题—day02
牛客–BM2 链表内指定区间反转
描述
将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。
例如:
给出的链表为 1→ 2 → 3 → 4 → 5 → NULL,1→2→3→4→5→NULL, m=2,n=4,
返回 1→ 4→ 3→ 2→ 5→ NULL
数据范围: 链表长度 0 < size \le 10000<siz**e≤1000,0 < m \le n \le size0/</m//≤n/≤siz**e,链表中每个节点的值满足 |val| \le 1000∣val∣≤1000
要求:时间复杂度 O(n),空间复杂度 O(n)
进阶:时间复杂度 O(n),空间复杂度 O(1)
示例1
输入:
{1,2,3,4,5},2,4
返回值:
{1,4,3,2,5}
复制
示例2
输入:
{5},1,1
返回值:
{5}
题解
/*
* function ListNode(x){
* this.val = x;
* this.next = null;
* }
*/
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
function reverseBetween( head , m , n ) {
// write code here
//如果 m === n,则不需要翻转
if(m === n){
return head;
}
//如果只有一个节点也不需要翻转
if(head === null || head.next === null){
return head;
}
//start指向开始翻转节点的前一个节点,即 m -1
//end 指向n+1
let cur = head;
let start = null;
let end = null;
//寻找start和end
for(let i = 1;i <= n; i++){
if(i === m-1){
start = cur;
}
cur = cur.next;
}
end = cur;
let pre = null
let next = null
//如果起始节点不是头节点,说明start有值
if(m>1){
cur = start.next;
while(cur !== end){
next = cur.next
cur.next = pre
pre = cur
cur = next
}
start.next.next = end
start.next = pre
}else{
// 起始节点就是头节点,start没有值
cur = head
while(cur !== end){
next = cur.next
cur.next = pre
pre = cur
cur = next
}
head.next = end
head = pre
}
return head;
}
module.exports = {
reverseBetween : reverseBetween
};
思路:
确定边界条件:
- 如果m和n相等,则不需要翻转,直接返回链表
- 如果输入的是空链表或只有一个结点,则直接返回链表
第二步:
定义两个指针,pre(指向m-1的结点)=null,end(指向n+1的结点)
cur指向当前结点head;
第三步:遍历,寻找pre和end;将pre指向m-1的结点,end指向n+1的结点
不过会有特殊情况,m=1,pre指向null,n是最后一个结点的话,end指向null
第四步,定义两个指针,pre和end
再遍历一下链表,进行反转,有个判断,当m>1时,说明有值,翻转是局部的。
如果m不>1,那就是从头就开始翻转
牛客–BM64 最小花费爬楼梯
描述
给定一个整数数组 cost,其中 cost[i] 是从楼梯第i 个台阶向上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
数据范围:数组长度满足 1≤n≤10^5 ,数组中的值满足1≤cost≤ 10^4
示例1
输入:[2,5,20]
返回值:5
说明:你将从下标为1的台阶开始,支付5 ,向上爬两个台阶,到达楼梯顶部。总花费为5
示例2
输入:[1,100,1,1,1,90,1,1,80,1]
返回值:6
说明:你将从下标为 0 的台阶开始。
1.支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
2.支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
3.支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
4.支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
5.支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
6.支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。
请你计算并返回达到楼梯顶部的最低花费。
题解
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param cost int整型一维数组
* @return int整型
*/
function minCostClimbingStairs( cost ) {
// write code here
let arr=[0,0];
for(let i=2;i<=cost.length;i++){
arr[i]=Math.min(arr[i-1]+cost[i-1],arr[i-2]+cost[i-2]);
}
return arr[cost.length];
}
module.exports = {
minCostClimbingStairs : minCostClimbingStairs
};
思路
用了dp动态规划,每跳一次,只会有两个情况,要么是前一级台阶向上一级,要么就是前两级的台阶向上两步,因为在前面的台阶花费我们都得到了,因此每次更新最小值即可,转移方程为: dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
返回的是dp数组中最后一个,即cost.length。
复杂度:
- 时间复杂度:O(n)
- 空间复杂度: O(n)
牛客–BM88 判断是否为回文字符串
描述
给定一个长度为 n 的字符串,请编写一个函数判断该字符串是否回文。如果是回文请返回true,否则返回false。
字符串回文指该字符串正序与其逆序逐字符一致
数据范围:0<n≤1000000
要求:空间复杂度 O(1),时间复杂度 O*(n)
示例1
输入:"absba"
返回值:true
示例2
输入:"ranko"
返回值:false
示例3
输入:"yamatomaya"
返回值:false
示例4
输入:"a"
返回值:true
题解
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param str string字符串 待判断的字符串
* @return bool布尔型
*/
function judge( str ) {
// write code here
let restr = str.split("").reverse().join("");
if(restr === str){
return true;
}
return false
//简便写法
//return str.split("").reverse().join("") === str;
}
module.exports = {
judge : judge
};
思路
利用js的方法进行反转,再判断。
牛客—BM83 字符串变形
描述
对于一个长度为 n 字符串,我们需要对它做一些变形。
首先这个字符串中包含着一些空格,就像"Hello World"一样,然后我们要做的是把这个字符串中由空格隔开的单词反序,同时反转每个字符的大小写。
比如"Hello World"变形后就变成了"wORLD hELLO"。
数据范围:1≤ n≤10^6,字符串中包括大写英文字母、小写英文字母、空格。
进阶:空间复杂度 O(n) , 时间复杂度 O(n)
输入描述:
给定一个字符串s以及它的长度n(1≤ n≤10^6)
返回值描述:
请返回变形后的字符串。题目保证给定的字符串均由大小写字母和空格构成。
示例1
输入:"This is a sample",16
返回值:"SAMPLE A IS tHIS"
示例2
输入:"nowcoder",8
返回值:"NOWCODER"
示例3
输入:"iOS",3
返回值:"Ios"
题解
function trans(s, n){
//write code here
//如果输入的字符串和长度是空的,则返回空字符串
if(!s || !n){
return '';
}
//定义一个栈stack
const stack = [];
//定义栈的元素
let stackEle = '';
//定义转换后的字符串
let transformWord = '';
//循环更改
for(let i = 0; i < n; i++){
//取逐个字符
const asciiCode = s.charCodeAt(i);
if(asciiCode >= 65 && asciiCode <= 90){
transformWord += s[i].toLowerCase();
}else if(asciiCode >= 97 && asciiCode <= 122){
transformWord += s[i].toUpperCase();
}else {
//空格
const ele = ' ' + transformWord;
stack.push(ele);
transformWord = '';
}
}
//当循环结束后,栈里最后一个单词没有入栈,是因为循环结束后,transformWord的值就是第一次出栈的结果
while(stackEle = stack.pop()){
transformWord += stackEle;
}
return transformWord;
}
module.exports = {
trans : trans
}
思路:
利用栈的特性
- 遍历每个字符,并且转换大小写,除空格以外。
- 若遍历到空格则把前面遍历的字符视为一个单词,加入栈,并在单词前面拼接一个空格,这样再出栈拼接单词时就有了空格。
- 出栈拼接单词,返回最后的结果
复杂度:
- 时间复杂度:O(n)
- 空间复杂度: O(n)
牛客–BM84 最长公共前缀
描述
给你一个大小为 n 的字符串数组 strs ,其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀,返回这个公共前缀。
数据范围:0≤n≤5000,0≤len(strs)≤5000
进阶:空间复杂度 O(n),时间复杂度O(n)
示例1
输入:["abca","abc","abca","abc","abcc"]
返回值:"abc"
示例2
输入:["abc"]
返回值:"abc"
方法一—子串纵向查找
题解
/**
*
* @param strs string字符串一维数组
* @return string字符串
*/
function longestCommonPrefix( strs ) {
// write code here
//如果数组为空,则返回空值
if(strs.length === 0){
return '';
}
if(strs.length == 1) return strs[0];
for(let i = 0 ;i<strs[0].length;i++){
for(let j = 1;j<strs.length;j++){
if(strs[0][i]!==strs[j][i]||i===strs[j].length){
return strs[0].substr(0,i);
}
}
}
return strs[0];
}
module.exports = {
longestCommonPrefix : longestCommonPrefix
};
思路:
纵向遍历将每个字符串分别依次遍历每一列的元素,比较相同列上字符是否相同,若相同则比较下一个子串,若不同则最长公共前缀为上个遍历过的公共前缀。
复杂度分析:
时间复杂度:O(mn),其中n是字符串的数量,m是字符串数组中的字符串的平均长度。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。
空间复杂度:O(1)。额外空间复杂度为常数。
方法二—排序后子串纵向查找
题解:
/**
*
* @param strs string字符串一维数组
* @return string字符串
*/
function longestCommonPrefix( strs ) {
// write code here
//如果数组为空,则返回空值
if(strs.length === 0){
return '';
}
if(strs.length == 1) return strs[0];
let newstr = strs.sort();
let arrA =newstr[0];
let arrB = newstr[newstr.length-1];
for(var i = 0 ;i<arrA.length&&(arrA[i]===arrB[i]);i++);//循环第一个子串的每个字符
return arrA.substr(0,i);
}
module.exports = {
longestCommonPrefix : longestCommonPrefix
};
思路:
先将所有子字符串按照字典序的大小排序,现只需要比较字典序最小的子串A与字典序最大的B比较相同部分,得到最长公共前缀就是所有子串的公共前缀。
时间复杂度:O(nlogn),其中n是字符串的数量,排序算法时间复杂度O(nlogn)。
空间复杂度:O(1)。额外空间复杂度为常数。
牛客-----BM86 大数加法
描述
以字符串的形式读入两个数字,编写一个函数计算它们的和,以字符串形式返回。
数据范围:s.length,t.length ≤ 100000,字符串仅由’0’~‘9’构成
要求:时间复杂度 O(n)
示例1
输入:"1","99"
返回值:"100"
说明:1+99=100
示例2
输入:"114514",""
返回值:"114514"
题解:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 计算两个数之和
* @param s string字符串 表示第一个整数
* @param t string字符串 表示第二个整数
* @return string字符串
*/
function solve( s , t ) {
// write code here
let sp = s.length-1;
let tp = t.length-1;
let resArr = [];
let p = 0;
let sum = 0;
while(sp >= 0 || tp >= 0 || p !== 0){
let sV = sp >= 0 ? s[sp] - 0 : 0;//s[sp]-0 相当于parseInt(s[i]);
let tV = tp >= 0 ? t[tp] - 0 : 0;
sum = sV + tV + p;
resArr.unshift(sum % 10);
p = sum >= 10 ? 1 : 0;
sp--;
tp--;
}
return resArr.join('');
}
module.exports = {
solve : solve
};
思路
模拟法(双尾指针法)
思路:模拟我们日常加法进位过程,详细过程看代码
时间复杂度:O(n),其中 n 为较长字符的长度,遍历字符串
空间复杂度:O(1),常数级空间