Java解leetcode,助力面试之简单10道题(四)
第169题 多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入 | 输出 |
---|---|
[3,2,3] | 3 |
示例 2:
输入 | 输出 |
---|---|
[2,2,1,1,1,2,2] | 2 |
解题思路
最简单的方法就是排序,排序之和找中间元素,因为多数元素出现次数超过了一半,所以中间元素一定是多数元素
代码
// 多数元素:排序
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length / 2];
}
}
时间复杂度为O(nlog n),数组长度为n
空间复杂度为O(log n)
第172题 阶乘后的零
给定一个整数 n,返回 n! 结果尾数中零的数量。
示例 1:
输入 | 输出 |
---|---|
3 | 0 |
解释: 3! = 6, 尾数中没有零。
示例 2:
输入 | 输出 |
---|---|
5 | 1 |
解释: 5! = 120, 尾数中有 1 个零.
解题思路
本题可以转换为计算n可以由几个5和其他小于5的数相乘,最后返回因子5的个数
代码
// 阶乘后的零:数学计算因子5
class Solution {
public int trailingZeroes(int n) {
int zeroCount = 0;
while (n > 0) {
n /= 5;//计算因子5的个数
zeroCount += n;
}
return zeroCount;
}
}
时间复杂度为O(log n),n为数字大小
空间复杂度为O(1)
第190题 颠倒二进制位
颠倒给定的 32 位无符号整数的二进制位。
示例 1:
输入 | 输出 |
---|---|
00000010100101000001111010011100 | 00111001011110000010100101000000 |
解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:
输入 | 输出 |
---|---|
11111111111111111111111111111101 | 10111111111111111111111111111111 |
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
示例 3:
输入 | 输出 |
---|---|
n = 00000010100101000001111010011100 | 964176192 (00111001011110000010100101000000) |
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数43261596,因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 4:
输入 | 输出 |
---|---|
n = 11111111111111111111111111111101 | 3221225471 (10111111111111111111111111111111) |
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
解题思路
翻转一个二进制数,可以先将它分成左右两部分,对每部分递归执行翻转操作,然后将左半部分拼接到右半部分,即完成翻转,对每一层,将每两位分一组,按组号取奇数组和偶数组,将偶数组移动到奇数组上,将奇数组移动到偶数组上
代码
// 颠倒二进制位:位运算分治
public class Solution {
private static final int M1 = 0x55555555; // 01010101010101010101010101010101
private static final int M2 = 0x33333333; // 00110011001100110011001100110011
private static final int M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
private static final int M8 = 0x00ff00ff; // 00000000111111110000000011111111
public int reverseBits(int n) {
n = n >>> 1 & M1 | (n & M1) << 1;//假设原数组位abcdefgh,这一步实现1位数的奇偶互换,变成badcfehg
n = n >>> 2 & M2 | (n & M2) << 2;//这一步实现2位数的奇偶变换,变成dcbaghef
n = n >>> 4 & M4 | (n & M4) << 4;//这一步实现4位数的奇偶变换,变成ghfedcba
n = n >>> 8 & M8 | (n & M8) << 8;//这一步实现8位数的奇偶变换,由于只给出了8位数,所以交换位置后,仍为ghfedcba
return n >>> 16 | n << 16;//交换16位数
}
}
时间复杂度为O(1)
空间复杂度为O(1)
第203题 移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例 1:
输入 | 输出 |
---|---|
head = [1,2,6,3,4,5,6], val = 6 | [1,2,3,4,5] |
示例 2:
输入 | 输出 |
---|---|
head = [], val = 1 | [] |
示例 3:
输入 | 输出 |
---|---|
head = [7,7,7,7], val = 7 | [] |
解题思路
使用链表遍历,如果当前指针的下一节点值为目标值,则将指针指向下一节点的next,如果当前指针的下一节点值不是目标值,则将当前指针等于下一指针。可以使用哑节点存储头节点
代码
// 移除链表元素:链表遍历
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;//哑节点存储头节点
ListNode temp = dummyHead;//建立一个遍历链表的节点
while(temp.next!=null){//循环条件,如果当前指针的下一节点不为空
if(temp.next.val == val){//判断值
temp.next = temp.next.next;//如果值等于目标值,则跳过下一节点
}else{
temp = temp.next;//否则指向下一节点
}
}
return dummyHead.next;//返回哑节点的下一节点
}
}
时间复杂度为O(n),n为链表节点个数
空间复杂度为O(1)
第204题 计数质数
统计所有小于非负整数n的质数的数量。
示例 1:
输入 | 输出 |
---|---|
n = 10 | 4 |
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入 | 输出 |
---|---|
n = 0 | 0 |
示例 3:
输入 | 输出 |
---|---|
n = 1 | 1 |
解题思路
使用枚举的方法,挨个遍历判断是否为质数
代码
// 计数质数:数学枚举
class Solution {
public int countPrimes(int n) {
int ans = 0;//统计结果
for (int i = 2; i < n; ++i) {
ans += isPrime(i) ? 1 : 0;//判断是否为质数,是则加1,不是则加0
}
return ans;
}
public boolean isPrime(int x) {//判断质数的函数
for (int i = 2; i * i <= x; ++i) {
if (x % i == 0) {
return false;//如果能够除尽i,则代表不是质数
}
}
return true;//遍历完成后仍不能除尽,则代表是质数
}
}
时间复杂度为O(n√n),n为数的大小
空间复杂度为O(1)
第205题 同构字符串
给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
示例 1:
输入 | 输出 |
---|---|
s = “egg”, t = “add” | true |
示例 2:
输入 | 输出 |
---|---|
s = “foo”, t = “bar” | false |
示例 3:
输入 | 输出 |
---|---|
s = “paper”, t = “title” | true |
解题思路
维护两张哈希表,一张存放字符串s,一张存放字符串t,同时遍历两张表,不断更新两张表,如果出现冲突,即当前下标对应的字符已在表中存在,且原来的下标不相等,则返回false,如果遍历结束没有出现冲突,则返回true
代码
// 同构字符串:哈希表
class Solution {
public boolean isIsomorphic(String s, String t) {
Map<Character, Character> s2t = new HashMap<Character, Character>();
Map<Character, Character> t2s = new HashMap<Character, Character>();//维护两张哈希表,一张存放字符串s,一张存放字符串t
int len = s.length();
for (int i = 0; i < len; ++i) {//遍历字符串
char x = s.charAt(i), y = t.charAt(i);
if ((s2t.containsKey(x) && s2t.get(x) != y) || (t2s.containsKey(y) && t2s.get(y) != x)) {//如果字符串s中已存在当前遍历到的值,且该值在字符串t中的索引不等于在字符串s中的索引时,或者字符串t中已存在当前遍历到的值,且该值在字符串s中的索引不等于在字符串t中的索引,则代表有冲突
return false;
}
s2t.put(x, y);
t2s.put(y, x);//如果没冲突则存入值
}
return true;//遍历结束仍无冲突,则返回true
}
}
时间复杂度为O(n),n为字符串长度
空间复杂度为O(∣Σ∣),Σ为字符串的字符集
第206题 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入 | 输出 |
---|---|
head = [1,2,3,4,5] | [5,4,3,2,1] |
示例 2:
输入 | 输出 |
---|---|
head = [1,2] | [2,1] |
示例 3:
输入 | 输出 |
---|---|
head = [] | [] |
解题思路
定义一个指针来实现相邻指针之间的交换,这里定义的是prev,然后不断递归交换指针
代码
// 反转链表:链表递归
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null,curr = head;
while(curr != null){
ListNode next = curr.next;//定义next指针为当前指针的下一指针
curr.next = prev;//让当前指针向前指
prev = curr;//令这个定义的指针指向当前位置
curr = next;//到这一步之前以及完成指针的交换,这一步的目的是移动指针到下一节点,再次递归实现相邻指针交换
}
return prev;
}
}
时间复杂度为O(n),n为链表长度
空间复杂度为O(1)
第225题 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通队列的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
示例 1:
输入 | 输出 |
---|---|
[“MyStack”, “push”, “push”, “top”, “pop”, “empty”] [[], [1], [2], [], [], []] | [null, null, null, 2, 2, false] |
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
解题思路
本题唯一一个难点就是要实现栈的先进后出,后进先出的操作,因此要将队列元素倒着放入,所以每插入一个元素都会执行队列长度大小的插入出队元素来实现倒序插入
代码
// 用队列实现栈:队列
class MyStack {
Queue<Integer> queue;//建立队列
public MyStack() {
queue = new LinkedList<Integer>();//定义队列为一个链表
}
public void push(int x) {//实现入栈操作
int n = queue.size();
queue.offer(x);//入队
for (int i = 0; i < n; i++) {
queue.offer(queue.poll());//将队列中删除的元素加入队列中,直到遍历完,将元素倒着加入到了队中
}
}
public int pop() {//实现出栈操作
return queue.poll();//出队
}
public int top() {//得到栈顶元素
return queue.peek();//得到出队的首元素
}
public boolean empty() {//判断是否为空栈
return queue.isEmpty();//判断是否为空队
}
}
时间复杂度为O(n),入栈操作为O(n),其余操作为O(1)
空间复杂度为O(n),n为栈内元素大小
第226题 翻转二叉树
翻转一棵二叉树。
示例 1:
输入: 4 输出: 4
/ \ / \
2 7 7 2
/ \ / \ / \ / \
1 3 6 9 9 6 3 1
输入 | 输出 |
---|---|
[4,2,7,1,3,6,9] | [4,7,2,9,6,3,1] |
解题思路
本题使用递归交换左右节点
代码
// 翻转二叉树:递归
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}//判断节点是否为空
TreeNode left = invertTree(root.left);//用left存储根节点的左节点
TreeNode right = invertTree(root.right);//用right存储根节点的右节点
root.left = right;
root.right = left;//交换左右节点
return root;
}
}
时间复杂度为O(n),n为树的节点个数
空间复杂度为O(n)
第231题 2 的幂
给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。
如果存在一个整数 x 使得 n == x个2相乘 ,则认为 n 是 2 的幂次方。
示例 1:
输入 | 输出 |
---|---|
n = 1 | true |
2的0次方为1
示例 2:
输入 | 输出 |
---|---|
n = 16 | true |
2的4次方为16
示例 3:
输入 | 输出 |
---|---|
n = 3 | false |
示例 4:
输入 | 输出 |
---|---|
n = 4 | true |
示例 5:
输入 | 输出 |
---|---|
n = 5 | false |
解题思路
运用数学方法,如果n是2的倍数,那它只可能是1,10,100,1000,10000等,它减去1为0,01,011,0111,01111,所以n&(n-1)如果等于0,则代表它是2的倍数
代码
// 2 的幂:数学
class Solution {
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
}
时间复杂度为O(1)
空间复杂度为O(1)