给定一个字符串s,找出其中不含有重复字符的最长子串的长度。这道题是字符串处理中的经典问题,考察对滑动窗口和哈希表的综合运用能力。
核心思路是维护一个滑动窗口,窗口内的字符都是不重复的。通过不断调整窗口的左右边界,找到最大的窗口大小。具体实现时,使用哈希表记录每个字符最后一次出现的位置,当遇到重复字符时,快速调整窗口左边界。
java复制public int lengthOfLongestSubstring(String s) {
if(s.length()==0){
return 0;
}
HashMap<Character,Integer> map=new HashMap<>();
int max=0,left=0;
for(int i=0;i<s.length();i++){
if(map.containsKey(s.charAt(i))){
left=Math.max(left,map.get(s.charAt(i))+1);
}
map.put(s.charAt(i),i);
max=Math.max(max,i-left+1);
}
return max;
}
关键点解析:
left表示窗口的左边界,i表示窗口的右边界left更新为重复字符上次出现位置的下一个位置注意:这里使用
Math.max(left, map.get(s.charAt(i))+1)是为了确保left不会回退。比如字符串"abba",当i=3时,如果不取最大值,left会从2回退到1。
时间复杂度:O(n),其中n是字符串长度。每个字符最多被访问两次(一次放入哈希表,一次检查是否重复)。
空间复杂度:O(min(m, n)),其中m是字符集大小。最坏情况下需要存储所有不同字符。
给定单链表的头指针head和两个整数left和right(left ≤ right),反转从位置left到位置right的链表节点,返回反转后的链表。
基本思路是找到需要反转的子链表,进行反转,然后重新连接。使用虚拟头节点可以简化边界条件处理。
java复制public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy=new ListNode(-1);
dummy.next=head;
ListNode pre=dummy;
for(int i=1;i<left;i++){
pre=pre.next;
}
ListNode cur=pre.next;
ListNode next;
for(int i=0;i<right-left;i++){
next =cur.next;
cur.next=next.next;
next.next=pre.next;
pre.next=next;
}
return dummy.next;
}
关键步骤解析:
dummy,简化头节点处理pre指针移动到反转区间的前一个节点cur指针指向需要反转的第一个节点right-left次循环,每次将cur.next节点插入到pre之后dummy.next以链表1→2→3→4→5,left=2,right=4为例:
初始状态:
dummy→1→2→3→4→5
pre指向1,cur指向2
第一次循环:
第二次循环:
提示:在实际面试中,建议先画出链表变化的示意图,再编写代码,可以大大减少出错概率。
给定一个已排序的链表,删除所有含有重复数字的节点,只保留原始链表中没有重复出现的数字。
解决思路:
java复制public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode prev = dummy;
ListNode curr = head;
while (curr != null) {
while (curr.next != null && curr.val == curr.next.val) {
curr = curr.next;
}
if (prev.next == curr) {
prev = prev.next;
} else {
prev.next = curr.next;
}
curr = curr.next;
}
return dummy.next;
}
关键点:
时间复杂度:O(n),每个节点最多被访问两次
空间复杂度:O(1),只使用了常数个额外指针
优化方向:
给定一个单链表L的头节点head,将其重新排列为:L0→Ln→L1→Ln-1→L2→Ln-2→...
解决思路:
java复制public void reorderList(ListNode head) {
if (head == null || head.next == null) return;
// 1. 找到中点
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 2. 反转后半部分
ListNode prev = null, curr = slow, tmp;
while (curr != null) {
tmp = curr.next;
curr.next = prev;
prev = curr;
curr = tmp;
}
// 3. 合并两个链表
ListNode first = head, second = prev;
while (second.next != null) {
tmp = first.next;
first.next = second;
first = tmp;
tmp = second.next;
second.next = first;
second = tmp;
}
}
快慢指针是链表问题中的常用技巧:
注意:不同的题目可能对中点的定义不同,比如要求偶数长度时返回第一个中点还是第二个中点,这会影响循环条件的设置。
链表反转是基本功,常见的实现方式有:
java复制public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode p = reverseList(head.next);
head.next.next = head;
head.next = null;
return p;
}
java复制public ListNode reverseList(ListNode head) {
ListNode dummy = new ListNode(-1);
while (head != null) {
ListNode next = head.next;
head.next = dummy.next;
dummy.next = head;
head = next;
}
return dummy.next;
}
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串""。
基本思路:
java复制public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) return "";
String prefix = strs[0];
for (int i = 1; i < strs.length; i++) {
while (strs[i].indexOf(prefix) != 0) {
prefix = prefix.substring(0, prefix.length() - 1);
if (prefix.isEmpty()) return "";
}
}
return prefix;
}
优化方向:
最坏时间复杂度:O(S),其中S是所有字符串中字符的总数
最好时间复杂度:O(minLen * n),其中minLen是最短字符串长度,n是字符串数量
在实际面试中,建议先提出最直观的解法,然后讨论可能的优化方向,展示你的思考过程。