leetcode刷题(一):字符串和栈

76 阅读4分钟

字符串

leetcode14题最长公共前缀:编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 ""
思路:声明一个变量保存公共前缀,遍历字符串数组中的一个字符串,判断其他字符串相同位置的字符是否相同,如果不同,直接返回公共前缀,相同则公共前缀拼接上当前字符。

function longestCommonPrefix(strs: string[]): string {
  let commonPrefix = "",
      curI = 0, // 记录当前比较字符索引
      firststr = strs[0],
      len = strs.length;
  for(let val of firststr) {
    // 遍历其余数组项
    for(let i = 1; i < len; i++) {
      if (strs[i][curI] !== val) return commonPrefix;
    }
    curI++
    commonPrefix += val
  }
  return commonPrefix;
};

leetcode3题无重复字符的最长子串:给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
思路:这是一个典型的考察双指针、滑动窗口的题目,我们声明两个指针,一个指针向右滑动,过程中判断左右指针之间有没有重复元素出现,出现的话左指针移动到重复元素的下一个元素,此过程还需要记录左右指针内最大元素的个数。

function lengthOfLongestSubstring(s: string): number {
  let left = 0, // 左指针
      map = new Map<string, number>(), // 保存元素和其索引的映射关系
      max = 0,
      len = s.length;
  // 右指针向右滑动
  for(let right = 0; right < len; right++) {
    const rightVal = s.charAt(right)
    // 查看map是否存在重复的值
    if(map.has(rightVal) && map.get(rightVal) >= left) {
      // 更新left的值
      left = map.get(rightVal) + 1
    }
    // 设置map键值
    map.set(rightVal, right);
    // 更新最大值
    max = Math.max(max, right - left + 1)
  }
  return max;
};

leetcode5题最长回文子串:给你一个字符串 s,找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
思路:回文字符串的核心就是对称,而对称最关键的就是什么样的对称中心是合格的。最容易想到的就是一个字符作为对称中心,然后两边对应的字符相同。其实多个相同的字符作为对称中心也是可以的,因为相同的这段字符不管是奇数个字符还是偶数个它自身都是对称的,只需要向两侧再拓展寻找即可,所以我们要尽可能的使用多个相同的字符串做为对称中心,按照这个思路我们来实现代码:

function longestPalindrome(s: string): string {
  let len = s.length, res = ''
  for(let i = 0; i < len; i++) {
    // 定义双指针
    let left = i - 1;
    let right = i + 1;
    // 检查左侧相同字符(可省略)
    // while(left >= 0 && s[left] === s[i]) {
    //    left--
    // }
    // 检查右侧相同字符
    while(right < len && s[right] === s[i]) {
      right++
      // 右侧字符与当前字符一样的话 右侧字符就不用再进入for循环执行相同的逻辑了 直接++即可
      // 这也是为什么不用检查左侧相同元素
      i++ 
    }
    // 检查两侧字符
    while(left >= 0 && right < len && s[left] === s[right]) {
      left--;
      right++
    }
    // 更新res
    if (right - left - 1 > res.length) {
      res = s.slice(left + 1, right)
    }
  }
  return res;
};

leetcode114题二叉树展开为链表:给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。 展开后的单链表应该与二叉树 先序遍历 顺序相同。
思路:二叉树的题目我们第一反应就是递归,这道题使用递归也确实很简单,但是我们思考一下使用栈怎么做。其实也很简单,利用栈的特性,将右节点左节点依次入栈,然后出栈,出栈元素如果有子节点则继续入栈,下面具体实现:

// 栈解法
function flatten(root: TreeNode | null): void {
  if (root === null) return;
  // 声明栈结构
  const stack = [root];
  let prev;
  while(stack.length) {
    const cur = stack.pop();
    // 子节点入栈
    if (cur.right) stack.push(cur.right);
    if (cur.left) stack.push(cur.left);
    if (prev) {
      // 设置prev左右子节点
      prev.right = cur
      prev.left = null
    }
    // 更新prev
    prev = cur
  }
};
// 递归解法
function flatten(root: TreeNode | null): void {
  if (root === null) return
  flatten(root.left);
  flatten(root.right);
  // 后续位置 左右节点已经拉成链表的形式
  let left = root.left, right = root.right
  root.left = null
  root.right = left
  while(left && left.right) {
    left = left.right
  }
  if (left) {
    left.right = right
  } else {
    root.right = right
  }
};

leetcode150题逆波兰表达式求值:给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。
思路:这道题主要还是要读懂什么是逆波兰表达式,其实就是两个数字在前面,计算的符号紧随其后,计算出来的结果还可以再和另外一个数字和符号进行运算。读懂了这个,其实就很清楚的知道要使用栈结构了:遇到数字我们就入栈,遇到符号我们就出栈两个元素进行运算,算出结果后再入栈,继续向后执行一样的步骤即可:

// leetcode 150
function evalRPN(tokens: string[]): number {
  // 声明栈
  const stack: number[] = [];
  let len = tokens.length;
  for (let i = 0; i < len; i++) {
    let preNumber: number, nextNumber: number;
    switch (tokens[i]) {
      case '+':
        nextNumber = stack.pop()!;
        preNumber = stack.pop()!;
        stack.push(preNumber + nextNumber);
        break;
      case '-':
        nextNumber = stack.pop()!;
        preNumber = stack.pop()!;
        stack.push(preNumber - nextNumber);
        break;
      case '*':
        nextNumber = stack.pop()!;
        preNumber = stack.pop()!;
        stack.push(preNumber * nextNumber);
        break;
      case '/':
        nextNumber = stack.pop()!;
        preNumber = stack.pop()!;
        stack.push(Math.trunc(preNumber / nextNumber));
        break;
      default:
        stack.push(+tokens[i]);
        break;
    }
  }
  return stack.pop()!;
}

剑指 Offer 09. 用两个栈实现队列:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 ) 思路:定义两个栈,只往栈1里加元素,只从第二个栈取元素,取元素之前要判断栈2是不是空的,如果是的话需要把栈1中的元素全部出栈加到栈2中:

// leetcode 剑指offer9
class CQueue {
  private stack1: number[] = []
  private stack2: number[] = []
  constructor() {

  }
  appendTail(value: number): void {
    this.stack1.push(value);
  }
  deleteHead(): number {
    if (!this.stack2.length) {
      while(this.stack1.length){
        this.stack2.push(this.stack1.pop()!)
      }
    }
    return this.stack2.pop() ?? -1;
  }
}