LeetCode题目解析
Easy
168. Excel表列名称
题目描述
给定一个正整数 columnNumber,返回它在 Excel 表中对应的列名称。
示例:
1 2 3 4 5 6 7 8
| 输入:columnNumber = 1 输出:"A"
输入:columnNumber = 28 输出:"AB"
输入:columnNumber = 701 输出:"ZY"
|
解题思路
这题本质上是一个特殊的 26 进制转换。
普通的进制转换通常从 0 开始计数,而 Excel 列名是从 1 开始计数:
1 2 3 4 5 6
| A -> 1 B -> 2 ... Z -> 26 AA -> 27 AB -> 28
|
因此在每一轮取余之前,需要先执行 columnNumber--,把 1 ~ 26 映射成 0 ~ 25。
然后通过:
1
| char ch = (char) ('A' + remainder);
|
就可以得到当前位对应的字母。
由于每次得到的是最低位字符,所以需要把字符插入到结果字符串的最前面。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package easy;
public class _168ExcelSheetColumnTitle { public String convertToTitle(int columnNumber) { StringBuilder result = new StringBuilder(); int remainder; while (columnNumber != 0) { columnNumber--; remainder = columnNumber % 26; char ch = (char) ('A' + remainder); columnNumber /= 26; result.insert(0, ch); } return result.toString(); } }
|
复杂度分析
- 时间复杂度:
O(log26 n),每次循环都会将 columnNumber 除以 26。
- 空间复杂度:
O(log26 n),结果字符串的长度与转换后的列名长度有关。
易错点
- 不能直接对
columnNumber 取余,否则 26 会得到 0,无法正确映射到 Z。
- 每轮循环都要先执行
columnNumber--,再进行取余和除法操作。
169.多数元素
题目描述
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例:
1 2 3 4 5
| 输入:nums = [3,2,3] 输出:3
输入:nums = [2,2,1,1,1,2,2] 输出:2
|
解题思路
使用摩尔投票算法(Boyer-Moore)。
这个算法专门解决出现次数>1/2的情况,使用互相抵消的机制,如果前一位和后一位不同,则标记值-1,到0则换为下一个元素进行标记。
既然最多的数出现大于1/2,那么最后抵消完之后,剩余的肯定是标记值,即可找到要求的元素。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package easy;
public class _169MajorityElement { public int majorityElement(int[] nums) { int candidate = 0; int count = 0;
for (int num : nums) { if (count == 0) { candidate = num; } count += candidate == num ? 1 : -1; }
return candidate; } }
|
复杂度分析
- 时间复杂度:
O(n),每次循环都会将 columnNumber 除以 26。
- 空间复杂度:
O(1),结果字符串的长度与转换后的列名长度有关。
171.Excel表列序号
题目描述
给定一个字符串 columnTitle,表示 Excel 表格中的列名称,返回该列名称对应的列序号。
示例:
1 2 3 4 5 6 7 8
| 输入:columnTitle = "A" 输出:1
输入:columnTitle = "AB" 输出:28
输入:columnTitle = "ZY" 输出:701
|
解题思路
这题和第 168 题正好相反:第 168 题是把数字转换成 Excel 列名,而本题是把 Excel 列名转换成数字。
Excel 列名可以看成一个 26 进制数,只不过每一位不是从 0 开始,而是从 1 开始:
1 2 3 4
| A -> 1 B -> 2 ... Z -> 26
|
例如 AB:
1 2 3
| A 位于高位,表示 1 * 26 B 位于低位,表示 2 * 1 结果为 1 * 26 + 2 = 28
|
遍历字符串时,当前字符对应的数值可以通过:
1
| columnTitle.charAt(i) - 'A' + 1
|
得到。
然后根据当前字符所在的位置,乘上对应的 26 的幂次,并累加到结果中即可。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12
| package easy;
public class _171ExcelSheetColumnNumber { public int titleToNumber(String columnTitle) { int count = 0; for (int i = 0; i < columnTitle.length(); i++) { count += (int) ((columnTitle.charAt(i) - 'A' + 1) * Math.pow(26, columnTitle.length() - i - 1)); } return count; } }
|
复杂度分析
- 时间复杂度:
O(n),其中 n 是字符串 columnTitle 的长度,需要遍历每一个字符。
- 空间复杂度:
O(1),只使用了常数个额外变量。
易错点
- 字符
A 对应的是 1,不是 0,所以需要加上 1。
- 越靠左的字符位权越高,需要乘以更高次的
26。
Math.pow 返回的是 double,本题最终结果是整数,所以需要强制转换为 int。
191.位1的个数
题目描述
给定一个正整数 n,编写一个函数,获取一个正整数的二进制形式并返回其二进制表达式中 设置位(1)的个数(也被称为汉明重量)。
示例 1:
1 2 3
| 输入:n = 11 输出:3 解释:输入的二进制串 1011 中,共有 3 个设置位。
|
示例 2:
1 2 3
| 输入:n = 128 输出:1 解释:输入的二进制串 10000000 中,共有 1 个设置位。
|
示例 3:
1 2 3
| 输入:n = 2147483645 输出:30 解释:输入的二进制串 1111111111111111111111111111101 中,共有 30 个设置位。
|
解题思路
使用除2取余法在转换为二进制的途中,判断是否为1,进行累加计算。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package easy;
public class _191NumberOf1Bits { public int hammingWeight(int n) { int count = 0; while (n != 0) { if (n % 2 == 1) { count++; } n /= 2; }
return count; } }
|
复杂度分析
时间复杂度:O(k),k 是二进制中 1 的个数,最多 32 次,所以也是 O(1)
空间复杂度:O(1)
202.快乐数
题目描述
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
解题思路
这道题的关键在于循环,一旦出现相同的数字则必定会成为一个循环,此外则会算到1。
方案一:使用HashSet记录出现过的数字,可以判断是否出现过相同的数字。
方案二:使用快慢指针,如果存在循环,则快慢指针一定会相遇。
代码实现
方案一:
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
| package easy;
import java.util.HashSet; import java.util.Set;
public class _202HappyNumber { public boolean isHappy(int n) { Set<Integer> set = new HashSet<>(); while (n != 1) { boolean flag = set.add(n);
if (!flag) { return false; } n = this.getNext(n); }
return true; }
public int getNext(int n) { int count = 0;
while (n != 0) { count += (int) Math.pow(n % 10, 2); n /= 10; }
return count; } }
|
方案二:
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
| package easy;
public class _202HappyNumber { public boolean isHappy(int n) { int slow = n; int fast = this.getNext(n);
while (slow != fast && fast != 1) { slow = this.getNext(slow); fast = this.getNext(this.getNext(fast)); }
return fast == 1; }
public int getNext(int n) { int count = 0;
while (n != 0) { count += (int) Math.pow(n % 10, 2); n /= 10; }
return count; } }
|
总结
如果遇到循环类型的题目,优先考虑使用快慢指针进行处理。
203.移除链表元素
题目描述
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
解题思路
使用哑结点(dummy node)可以避免对链表头结点进行特殊处理,最后取值直接返回dummyNode的next即可。
使用prev和curr两个指针指向前一位和当前位保证链表的接续。
代码实现
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
| package easy;
public class _203RemoveLinkedListElements { public ListNode removeElements(ListNode head, int val) { ListNode dummy = new ListNode(0); dummy.next = head; ListNode prev = dummy; ListNode curr = head; while (curr != null) { if (curr.val == val) { prev.next = curr.next; } else { prev = curr; } curr = curr.next; }
return dummy.next; }
public static class ListNode { int val; ListNode next;
ListNode() { }
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; } } }
|
Medium
hard