算法题
1、反转链表
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
数据范围: 10000≤n≤1000
要求:空间复杂度 O(1) ,时间复杂度 O*(*n) 。
如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
- 双指针(简单题)
// c++
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode *pre = nullptr;
ListNode *cur = pHead;
ListNode *nex = nullptr; // 这里可以指向nullptr,循环里面要重新指向
while (cur) {
nex = cur->next;
cur->next = pre;
pre = cur;
cur = nex;
}
return pre;
}
};
// java
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null) return head;
ListNode p = head;
ListNode q = p.next;
p.next = null;
while(q!=null){
ListNode n = q;
q = q.next;
n.next = p;
p = n;
}
return p;
}
}
2、合并有序列表
输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
数据范围: 10000≤n≤1000,1000−1000≤节点值≤1000
要求:空间复杂度 O(1),时间复杂度 O(n)如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6}
- 双指针
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode *vhead = new ListNode(-1);
ListNode *cur = vhead;
while (pHead1 && pHead2) {
if (pHead1->val <= pHead2->val) {
cur->next = pHead1;
pHead1 = pHead1->next;
}
else {
cur->next = pHead2;
pHead2 = pHead2->next;
}
cur = cur->next;
}
cur->next = pHead1 ? pHead1 : pHead2;
return vhead->next;
}
};
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode head = new ListNode(-1);
ListNode p = head;
while(list1!=null && list2 !=null){
if(list1.val < list2.val){
p.next = list1;
list1 = list1.next;
}else{
p.next = list2;
list2 = list2.next;
}
p = p.next;
}
if(list1!=null) p.next = list1;
if(list2!=null) p.next = list2;
return head.next;
}
}
3、数组中出现次数超过一半的数字
给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。
数据范围:n≤50000,数组中元素的值 100000≤val≤10000
要求:空间复杂度:O*(1),时间复杂度O(*n)
- 排序法,时间复杂度O(nlogn),空间O(1)
public int MoreThanHalfNum_Solution(int [] array) {
Arrays.sort(array);
int ans = array[0];
int count = 1;
for(int i = 1;i<array.length;i++){
if(array[i] == array[i-1]){
count++;
if(array.length / 2 < count){
ans = array[i];
}
}else{
count = 1;
}
}
return ans;
}
- 候选法(投票法),时间复杂度O(n),空间O(1),太牛了
- 众数一定大于数组的长度的一半
- 如果两个数不相等,就消去这两个数,最坏情况下,每次消去一个众数和一个非众数,那么如果存在众数,最后留下的数肯定是众数。
public int MoreThanHalfNum_Solution(int [] array) {
int cond = array[0];
int num = 1;
for(int i = 1;i<array.length;i++){
if(num == 0){
cond = array[i];
num = 1;
}else if(array[i]!=cond){
num--;
}else{
num++;
}
}
return cond;
}
4、有序数组中找绝对值最小的数
- 二分查找(据说还能优化,这个题剑指offer没有)
public int binarySearch(int[] arr, double num){
int left = 0;
int right = arr.length - 1;
while(left < right){
int mid = (left + right) / 2;
if(arr[mid]<num){
left = mid + 1;
}else if(arr[mid]>num){
right = mid - 1;
}else return mid;
}
return left;
}
public int find(int [] array) {
if(array[array.length - 1] <= 0) return array[array.length - 1];
if(array[0] >= 0) return array[0];
int ans = binarySearch(array, 0 );
if(ans + 1 < array.length-1)
return Math.abs(array[ans]) < Math.abs(array[ans+1])?array[ans]:array[ans+1];
return array[ans];
}
5、在二叉树中找到两个节点的最近公共祖先
给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。
数据范围:树上节点数满足1≤n≤105 , 节点值val满足区间 [0,n)
要求:时间复杂度 O(n)
注:本题保证二叉树中每个节点的val值均不相同。
- 栈+dfs:两次路径分开找
// c++
class Solution {
public:
//记录是否找到到o的路径
bool flag = false;
//求得根节点到目标节点的路径
void dfs(TreeNode* root, vector<int>& path, int o){
if(flag || root == NULL)
return;
path.push_back(root->val);
//节点值都不同,可以直接用值比较
if(root->val == o){
flag = true;
return;
}
//dfs遍历查找
dfs(root->left, path, o);
dfs(root->right, path, o);
//找到
if(flag)
return;
//该子树没有,回溯
path.pop_back();
}
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
vector<int> path1, path2;
//求根节点到两个节点的路径
dfs(root, path1, o1);
//重置flag,查找下一个
flag = false;
dfs(root, path2, o2);
int res;
//比较两个路径,找到第一个不同的点
for(int i = 0; i < path1.size() && i < path2.size(); i++){
if(path1[i] == path2[i])
//最后一个相同的节点就是最近公共祖先
res = path1[i];
else
break;
}
return res;
}
};
// java
import java.util.*;
public class Solution {
//记录是否找到到o的路径
public boolean flag = false;
//求得根节点到目标节点的路径
public void dfs(TreeNode root, ArrayList<Integer> path, int o){
if(flag || root == null)
return;
path.add(root.val);
//节点值都不同,可以直接用值比较
if(root.val == o){
flag = true;
return;
}
//dfs遍历查找
dfs(root.left, path, o);
dfs(root.right, path, o);
//找到
if(flag)
return;
//回溯
path.remove(path.size() - 1);
}
public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
ArrayList<Integer> path1 = new ArrayList<Integer>();
ArrayList<Integer> path2 = new ArrayList<Integer>();
//求根节点到两个节点的路径
dfs(root, path1, o1);
//重置flag,查找下一个
flag = false;
dfs(root, path2, o2);
int res = 0;
//比较两个路径,找到第一个不同的点
for(int i = 0; i < path1.size() && i < path2.size(); i++){
int x = path1.get(i);
int y = path2.get(i);
if(x == y)
//最后一个相同的节点就是最近公共祖先
res = x;
else
break;
}
return res;
}
}
6、快排模板
public void quickSort(int []arr,int left, int right){
if(left >= right) return; // 左边大于右边没有意义
int i = left - 1;
int j = right + 1;
int x = arr[left]; // 保存第一个值,以他为基准进行排序
while(i<j){
do{i++} while(arr[i] < x); // 如果小于这个值在左边
do{j--} while(arr[j] > x); // 如果大于这个值在右边
if(i<j){
// 跳出循环说明第i个和第j个不满足条件
// 交换他们的位置
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
quickSort(arr,left,j); // 将这个值左边排好序
quickSort(arr,j+1,right); // 将这个值右边排好序
}
7、数值的整数次方
实现函数 double Power(double base, int exponent),求base的exponent次方。
注意:
1.保证base和exponent不同时为0。
2.不得使用库函数,同时不需要考虑大数问题
3.有特殊判题,不用考虑小数点后面0的位数。
数据范围: ∣base∣≤100 ,∣exponent∣≤100 ,保证最终结果一定满足 ∣val∣≤104
空间复杂度 O(1),时间复杂度 O(n)
- 快速幂:当
y
y
y是偶数时
x
y
=
x
y
/
2
∗
x
y
/
2
x^y=x^{y/2}*x^{y/2}
xy=xy/2∗xy/2,当
y
y
y是奇数时
x
y
=
x
y
/
2
∗
x
y
/
2
∗
x
x^y=x^{y/2}*x^{y/2}*x
xy=xy/2∗xy/2∗x
- 坑点:y可能是负数,记得转换
- 时间复杂度:O(log2n),其中n为所求的次方数,快速幂相当于对求幂使用二分法
- 空间复杂度:O(1),常数级变量,无额外辅助空间
public double Power(double base, int exponent) {
if(exponent == 0) return 1;
if(exponent < 0) {
base = 1. / base;
exponent = - exponent;
}
if(exponent % 2 == 0){
return Power(base , exponent / 2) * Power(base , exponent / 2);
}else{
return Power(base , exponent / 2) * Power(base , exponent / 2) * base;
}
}
8、重排链表
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。要求:空间复杂度O*(*n) 并在链表上进行操作而不新建链表,时间复杂度 O(n)
进阶:空间复杂度 O*(1) , 时间复杂度 O(n)*
注:2022年百度面试题,2021年字节面试题
- 线性表
- 用数组存链表,然后进行插入,空间复杂度 O(n) , 时间复杂度 O(n)
- 吐槽:我好傻,想到用map存父节点居然没想到直接把链表转成线性的/(ㄒoㄒ)/~~
class Solution {
public:
void reorderList(ListNode *head) {
if (head == nullptr) {
return;
}
vector<ListNode *> vec;
ListNode *node = head;
while (node != nullptr) {
vec.emplace_back(node);
node = node->next;
}
int i = 0, j = vec.size() - 1;
while (i < j) {
vec[i]->next = vec[j];
i++;
if (i == j) {
break;
}
vec[j]->next = vec[i];
j--;
}
vec[i]->next = nullptr;
}
};
class Solution {
public void reorderList(ListNode head) {
if (head == null) {
return;
}
List<ListNode> list = new ArrayList<ListNode>();
ListNode node = head;
while (node != null) {
list.add(node);
node = node.next;
}
int i = 0, j = list.size() - 1;
while (i < j) {
list.get(i).next = list.get(j);
i++;
if (i == j) {
break;
}
list.get(j).next = list.get(i);
j--;
}
list.get(i).next = null;
}
}
- 寻找链表中点 + 链表逆序 + 合并链表
- 注意到目标链表即为将原链表的左半端和反转后的右半端合并后的结果,分三步
- 先找到链表的中间
- 把后面的反转
- 合并链表
- 空间复杂度 O(1) , 时间复杂度 O(n),太麻烦了,不写了
- 注意到目标链表即为将原链表的左半端和反转后的右半端合并后的结果,分三步
9、翻转单词顺序
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. “,则输出"student. a am I”。
- java工具类做太简单了,注意通过“ ”分割后如果有多个空格数组里面保存的是空""
- 从前往后遍历,遇见空格就跳过
class Solution {
public:
string reverseWords(string s) {
int left = 0, right = s.size() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s[left] == ' ') ++left;
// 去掉字符串末尾的空白字符
while (left <= right && s[right] == ' ') --right;
deque<string> d;
string word;
while (left <= right) {
char c = s[left];
if (word.size() && c == ' ') {
// 将单词 push 到队列的头部
d.push_front(move(word));
word = "";
}
else if (c != ' ') {
word += c;
}
++left;
}
d.push_front(move(word));
string ans;
while (!d.empty()) {
ans += d.front();
d.pop_front();
if (!d.empty()) ans += ' ';
}
return ans;
}
};
10、最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
- 动态规划模板题,时间复杂度:O(mn),空间复杂度:O(mn)
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.length(), n = text2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
for (int i = 1; i <= m; i++) {
char c1 = text1.at(i - 1);
for (int j = 1; j <= n; j++) {
char c2 = text2.at(j - 1);
if (c1 == c2) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
};
public int longestCommonSubsequence(String text1, String text2) {
int n = text1.length();
int m = text2.length();
int [][] dp = new int[n+1][m+1];
// i为第一个字符串的位置的下一个,j同理
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
if(text1.charAt(i-1) == text2.charAt(j-1)){
// 这两个值相等,两个字符串下一个字符的位置就等于这个值+1
dp[i][j] = dp[i-1][j-1] + 1;
}else{
// 这两个值不等,下一个位置就等于两个字符串前一个位置最大的
dp[i][j] = Math.max(dp[i][j-1],dp[i-1][j]);
}
}
}
return dp[n][m];
}
11、合并有序列表并去重
合并有序链表并去重,没什么好说的
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode *vhead = new ListNode(-1);
ListNode *cur = vhead;
while (pHead1 && pHead2) {
if (pHead1->val < pHead2->val) {
cur->next = pHead1;
pHead1 = pHead1->next;
}
else if(pHead1->val > pHead2->val) {
cur->next = pHead2;
pHead2 = pHead2->next;
}else{
cur->next = pHead1;
pHead1 = pHead1->next;
pHead2 = pHead2->next;
}
cur = cur->next;
}
cur->next = pHead1 ? pHead1 : pHead2;
return vhead->next;
}
};
12、合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
要求:时间复杂度 O ( n l o g k ) O(nlogk) O(nlogk)
- 从第一个链表开始,两两合并,时间复杂度为 O ( k 2 n ) O(k^2n) O(k2n) ,空间复杂度 O ( 1 ) O(1) O(1)
- 使用优先队列(堆排序),时间复杂度 O ( n l o g k ) O(nlogk) O(nlogk),空间复杂度 O ( n k ) O(nk) O(nk)
class Solution {
public:
struct Status {
int val;
ListNode *ptr;
bool operator < (const Status &rhs) const {
return val > rhs.val;
}
};
priority_queue <Status> q;
ListNode* mergeKLists(vector<ListNode*>& lists) {
for (auto node: lists) {
if (node) q.push({node->val, node});
}
ListNode head, *tail = &head;
while (!q.empty()) {
auto f = q.top(); q.pop();
tail->next = f.ptr;
tail = tail->next;
if (f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});
}
return head.next;
}
};
public ListNode mergeKLists(ArrayList<ListNode> lists) {
if(lists.size() == 0) return null;
ListNode root = new ListNode(-1);
ListNode p = root;
PriorityQueue<ListNode> queue = new PriorityQueue<>((o1, o2) -> o1.val - o2.val);
for(int i = 0;i<lists.size();i++){
ListNode head = lists.get(i);
while(head!=null){
queue.offer(head);
head = head.next;
}
}
int n = queue.size();
for(int i=0;i<n;i++){
p.next = queue.poll();
p = p.next;
}
p.next = null;
return root.next;
}
13、交换排序
一个数组有1,2,3三个元素,乱序排列,只能两两交换,将数组中的数排序
- 冒泡排序
public void BubbleSort(int[] arr){
for(int i = 0;i<arr.length;i++){
for(int j = i+1;j<arr.length;j++){
if(arr[i]>arr[j]){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
- 快速排序
public quickSort(int[] arr,int left, int right){
if(left>=right) return;
int i = left - 1;
int j = right + 1;
int x = arr[left];
while(i<j){
do{i++} while(arr[i]<x);
do{j--} while(arr[j]>x);
if(i<j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
quickSort(arr,left,j);
quickSort(arr,j+1,right);
}
智力题
1、一枚银币,不停地抛,第N次才正面朝上的期望
- 因为每次都是独立的,所以服从二项分布 B(n,p),此分布期望E=np
- 第n次才正面朝上,说明前n-1次都是反面朝上
2、N个人,每个人手中握有一个数,要得到这些数之和,又不能让别人知道自己手中的数是多少,怎么做到
- 同态加密
- 加密后的数据可以进行运算,得到结果后再解密
- 秘密共享
- 将自己手里面的数字拆分成一部分发送给另一个人,由另一个人加起来,最后汇总
- 例子:A:25, B:25, C:50
- A拆分成3块——A发给B:15——A发给C:5——A留:5
- B拆分成2块——B发给A:5——B发给C:20
- C拆分成2块——C发给A:15——C留:35
- 计算:A:5+5+15=25,B:15,C:5+35+20=60,总:25+15+40=100
- 将自己手里面的数字拆分成一部分发送给另一个人,由另一个人加起来,最后汇总
- 混淆电路