又是新的一周,工作加油,算法加油!
哎,昨天没睡好,好困啊!
算法题
1. 左旋转字符串
题目
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"
限制:
1 <= k < s.length <= 10000
题解
我的解
啊哈,这题还是比较好想思路。
我的思路是旋转其实就是位置变换,分割字符串之后变换位置加起来就可以了。
class Solution {
public String reverseLeftWords(String s, int n) {
String substring1 = s.substring(0, n);
String substring2 = s.substring(n);
return substring2+substring1;
}
}
官方解一:字符串切片
应用字符串切片函数,可方便实现左旋转字符串。
获取字符串 s[n:] 切片和 s[:n] 切片,使用 "+" 运算符拼接并返回即可。
复杂度分析:
- 时间复杂度 O(N) : 其中 N 为字符串 s 的长度,字符串切片函数为线性时间复杂度(参考资料);
- 空间复杂度 O(N) : 两个字符串切片的总长度为 N 。
代码:
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n, s.length()) + s.substring(0, n);
}
}
优化代码
class Solution {
public String reverseLeftWords(String s, int n) {
return (s+s).substring(n,n+s.length());
}
}
官方解二:列表遍历拼接
若面试规定不允许使用 切片函数 ,则使用此方法。
算法流程:
- 新建一个 list(Python)、StringBuilder(Java) ,记为 res ;
- 先向 res 添加 “第 n+1 位至末位的字符” ;
- 再向 res 添加 “首位至第 n 位的字符” ;
- 将 res 转化为字符串并返回。
复杂度分析:
- 时间复杂度 O(N) : 线性遍历 s 并添加,使用线性时间;
- 空间复杂度 O(N) : 新建的辅助 res 使用 O(N) 大小的额外空间。
代码:
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuilder res = new StringBuilder();
for(int i = n; i < s.length(); i++)
res.append(s.charAt(i));
for(int i = 0; i < n; i++)
res.append(s.charAt(i));
return res.toString();
}
}
利用求余运算,可以简化代码。
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuilder res = new StringBuilder();
for(int i = n; i < n + s.length(); i++)
res.append(s.charAt(i % s.length()));
return res.toString();
}
}
官方解三:字符串遍历拼接
若规定 Python 不能使用 join() 函数,或规定 Java 只能用 String ,则使用此方法。
此方法与 方法二 思路一致,区别是使用字符串代替列表。
复杂度分析:
- 时间复杂度 O(N) : 线性遍历 s 并添加,使用线性时间;
- 空间复杂度 O(N) : 假设循环过程中内存会被及时回收,内存中至少同时存在长度为 N 和 N-1 的两个字符串(新建长度为 N 的 res 需要使用前一个长度 N-1 的 res ),因此至少使用 O(N) 的额外空间。
class Solution {
public String reverseLeftWords(String s, int n) {
String res = "";
for(int i = n; i < s.length(); i++)
res += s.charAt(i);
for(int i = 0; i < n; i++)
res += s.charAt(i);
return res;
}
}
同理,利用求余运算,可以简化代码。
class Solution {
public String reverseLeftWords(String s, int n) {
String res = "";
for(int i = n; i < n + s.length(); i++)
res += s.charAt(i % s.length());
return res;
}
}
总结
哈哈,还是遇到这种简单题好啊,做出来就很舒服~~~
2. 宝石与石头
题目
给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。
J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。
示例 1:
输入: J = "aA", S = "aAAbbbb"
输出: 3
示例 2:
输入: J = "z", S = "ZZ"
输出: 0
注意:
- S 和 J 最多含有50个字母。
- J 中的字符不重复。
题解
我的解
我的思路是循环比较就完事了。
class Solution {
public int numJewelsInStones(String J, String S) {
int result = 0;
for (int i = 0; i < J.length(); i++) {
for (int j = 0; j < S.length(); j++) {
if (J.charAt(i) == S.charAt(j)){
result++;
}
}
}
return result;
}
}
官方解一: 暴力法
思路和算法
遍历每块石头,检查是不是宝石。检查步骤用简单的线性搜索来实现。
class Solution {
public int numJewelsInStones(String J, String S) {
int ans = 0;
for (char s: S.toCharArray()) // For each stone...
for (char j: J.toCharArray()) // For each jewel...
if (j == s) { // If the stone is a jewel...
ans++;
break; // Stop searching whether this stone 's' is a jewel
}
return ans;
}
}
复杂度分析
- 时间复杂度:O(J.length∗S.length))。
- 空间复杂度:在 Python 实现中,空间复杂度为 O(1)。在 Java 实现中,空间复杂度为 O(J.length∗S.length))。
官方解二: 哈希集合
思路和算法
遍历每块石头,检查是不是宝石。检查步骤用 哈希集合 来高效完成。
class Solution {
public int numJewelsInStones(String J, String S) {
Set<Character> Jset = new HashSet();
for (char j: J.toCharArray())
Jset.add(j);
int ans = 0;
for (char s: S.toCharArray())
if (Jset.contains(s))
ans++;
return ans;
}
}
复杂度分析
- 时间复杂度:O(J.length+S.length))。O(J.length}) 这部分来自于创建 J,O(S.length) 这部分来自于搜索 S。
- 空间复杂度:O(J.length)。
总结
太好啦!又做出来了(虽然很简单咳咳)!
有时候朴实无华的循环也不错!
Ps:好困啊~~~
3. 删除中间节点
题目
实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。
示例:
输入:单向链表a->b->c->d->e->f中的节点c
结果:不返回任何数据,但该链表变为a->b->d->e->f
题解
我的解
我有思路,就是删除那个借点不就行啦,但是思路不对!
官方解:改变节点指向
把要删除的节点替换为下一个节点就行了
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
总结
链表早就忘了......
补:链表的优势在于定点删除/插入元素,因为链表影响的最多就是给定元素的左右的两个链,但是数组却做不到,数组由于大小固定,删除操作会移动所有的元素。
4. IP 地址无效化
题目
给你一个有效的 IPv4 地址 address,返回这个 IP 地址的无效化版本。
所谓无效化 IP 地址,其实就是用 "[.]" 代替了每个 "."。
示例 1:
输入:address = "1.1.1.1"
输出:"1[.]1[.]1[.]1"
示例 2:
输入:address = "255.100.50.0"
输出:"255[.]100[.]50[.]0"
提示:
- 给出的 address 是一个有效的 IPv4 地址
题解
我的解
我的想法是分割字符串,手动变化再组合成新的字符串。值得一提的是“.”需要进行转义。
另外这样写虽然可以通过,但是效率好低啊~~~~
class Solution {
public String defangIPaddr(String address) {
String[] split = address.split("\\.");
return split[0] + "[.]" + split[1] + "[.]" + split[2] + "[.]" + split[3];
}
}
官方解一:内置 replace
比我的还简化,原来还有这么好的方法(doge)
比我的性能好不少啊!
class Solution {
public String defangIPaddr(String address) {
return address.replace(".", "[.]");
}
}
官方解二:遍历并修改
使用 address 创建 StringBuilder,遍历,遇到 '.' 使用 insert。
class Solution {
public String defangIPaddr(String address) {
StringBuilder s = new StringBuilder(address);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '.') {
s.insert(i + 1, ']');// 先插入后面,此时 i 下标仍是'.'
s.insert(i, '[');// 插入 '.' 前面,此时 i 下标是'[' ,i+2 下标为']'
i += 3;// 故 i 直接加 3,为下一个字符,注意此时已经是原来 i+1 下标的字符了;
//此次循环结束进入下次循环还会进行加 1,不过又因为 ip 地址格式的原因,不会有连续的两个 '.' 连着;
//所以这个位置绝不可能是 '.',所以再加 1,也没问题。
}
}
return s.toString();
}
}
官方解三:遍历并新增
创建空的 Stringbuilder,遍历 address 进行 append。
class Solution {
public String defangIPaddr(String address) {
StringBuilder s = new StringBuilder();
for (int i = 0; i < address.length(); i++) {
if (address.charAt(i) == '.') {
//s.append('[');
//s.append('.');
//s.append(']');
s.append("[.]");
} else {
s.append(address.charAt(i));
}
}
return s.toString();
}
}
总结
好吧,现在能做出来但是太拉胯了哈哈。
下一步试着多想几种方法来实现。
5. 整数的各位积和之差
题目
给你一个整数 n,请你帮忙计算并返回该整数「各位数字之积」与「各位数字之和」的差。
示例 1:
输入:n = 234
输出:15
解释:
各位数之积 = 2 * 3 * 4 = 24
各位数之和 = 2 + 3 + 4 = 9
结果 = 24 - 9 = 15
示例 2:
输入:n = 4421
输出:21
解释:
各位数之积 = 4 * 4 * 2 * 1 = 32
各位数之和 = 4 + 4 + 2 + 1 = 11
结果 = 32 - 11 = 21
提示:
- 1 <= n <= 10^5
题解
我的解
我的思路很清晰,但是一看就效率极低(doge)。
首先是得到这个数字是几位数,然后把数字的每一位求出来并放到一个数字数组中,最后遍历相加和相乘,思路简单,效率很低。
class Solution {
public int subtractProductAndSum(int n) {
// 首先得到数字的长度
String sn = n + "";
int length = sn.length(),jia = 0,cheng = 1;
// 遍历获取数字的各个位,加入新的整形数组
int arr[] = new int[length];
for (int i = length - 1; i >= 0; i--) {
if (i == 0){
arr[i] = n % 10;
}else {
int n1 = (int) Math.pow(10,i+1);
int n2 = (int) Math.pow(10,i);
arr[i] = (n % n1) / n2;
}
}
// 遍历 增加 和 相乘
for (int i = 0; i < length; i++) {
jia += arr[i];
cheng *= arr[i];
}
return cheng - jia;
}
}
官方解:模拟(暴力)
我们每次通过取模运算得到 n 的最后一位,依次进行乘法和加法运算,最后将得到的积 mul 以及和 add 相减即可得到答案。
class Solution {
public int subtractProductAndSum(int n) {
int s1 =1;
int t =0;
int s2=0;
while(n > 0){
t = n%10;
n = n/10;
//System.out.println(t);
s2 += t;
s1 *= t;
}
return s1 - s2;
}
}
总结
想法还有待改善,总是想的比答案麻烦啊~~~
6. 拿硬币
题目
桌上有 n 堆力扣币,每堆的数量保存在数组 coins 中。我们每次可以选择任意一堆,拿走其中的一枚或者两枚,求拿完所有力扣币的最少次数。
示例 1:
输入:[4,2,1]
输出:4
解释:第一堆力扣币最少需要拿 2 次,第二堆最少需要拿 1 次,第三堆最少需要拿 1 次,总共 4 次即可拿完。
示例 2:
输入:[2,3,10]
输出:8
限制:
- 1 <= n <= 4
- 1 <= coins[i] <= 10
题解
我的解
哈哈,又是比较简单的一道题!这次应该跟他安相差不远吧(心里想)。
class Solution {
public int minCount(int[] coins) {
int result = 0;
for (int i = 0; i < coins.length; i++) {
result += coins[i]/2 + coins[i]%2;
}
return result;
}
}
官方解:请看我的解
终于有一次跟官方解写的差不多了!哦也!叉会腰!
总结
看来刷力扣确实对写代码有提高呀嘿嘿!
7. 解压缩编码列表
题目
给你一个以行程长度编码压缩的整数列表 nums 。
考虑每对相邻的两个元素 [freq, val] = [nums[2*i], nums[2*i+1]] (其中 i >= 0 ),每一对都表示解压后子列表中有 freq 个值为 val 的元素,你需要从左到右连接所有子列表以生成解压后的列表。
请你返回解压后的列表。
示例:
输入:nums = [1,2,3,4]
输出:[2,4,4,4]
解释:第一对 [1,2] 代表着 2 的出现频次为 1,所以生成数组 [2]。
第二对 [3,4] 代表着 4 的出现频次为 3,所以生成数组 [4,4,4]。
最后将它们串联到一起 [2] + [4,4,4] = [2,4,4,4]。
示例 2:
输入:nums = [1,1,2,3]
输出:[1,3,3]
提示:
- 2 <= nums.length <= 100
- nums.length % 2 == 0
- 1 <= nums[i] <= 100
题解
我的解
我的思路是先找出写过数组的长度,然后循环对原来的数组进行比较,进行复制。
class Solution {
public int[] decompressRLElist(int[] nums) {
// 先求出结果数组的长度 便于构建结果数组
int length = 0;
for (int i = 0; i < nums.length; i=i+2) {
length += nums[i];
}
int result[] = new int[length];
// j 是每一组数的循环变量 change 是累加的变量
int j = 0,change = 0;
for (int i = 0; i < nums.length; i = i+2) {
// 第一次循环 change = 0,其他情况累加
if (i != 0){
change += nums[i-2];
}
// 循环为 结果数组 赋值
for (; j < change+nums[i]; j++) {
result[j] = nums[i+1];
}
}
return result;
}
}
官方解:遍历
class Solution {
public int[] decompressRLElist(int[] nums) {
//如果原数组的长度是否小于1,则返回原数组的第一个元素
if(nums.length <= 1){
return new int[]{nums[0]};
}
//初始化新数组的长度为0
int len = 0;
//遍历原数组,得到新数组的长度
for(int i=0; i<nums.length; i+=2){
len+=nums[i];
}
//初始化新数组
int[] res = new int[len];
//再一次遍历新数组
for(int i=0,j=0;i<nums.length;i+=2){
//对新数组里面的元素进行赋值
for(int k=0; k<nums[i]; k++){
res[j++] = nums[i+1];
}
}
//返回新数组
return res;
}
}
总结
虽然写出来,但是还是可以优化!
8. 删除链表中的节点
题目
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
现有一个链表 -- head = [4,5,1,9],它可以表示为:
4-->5-->1-->9
示例 1:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
提示:
- 链表至少包含两个节点。
- 链表中所有节点的值都是唯一的。
- 给定的节点为非末尾节点并且一定是链表中的一个有效节点。
- 不要从你的函数中返回任何结果。
题解
我的解
这个题怎么这么眼熟啊~~~嘿嘿
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
官方解:请看我的解!
总结
哈哈 果然只有究极简单的题我才能和官方答案一样啊!
9. 将数字变成 0 的操作次数
题目
给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。
示例 1:
输入:num = 14
输出:6
解释:
步骤 1) 14 是偶数,除以 2 得到 7 。
步骤 2) 7 是奇数,减 1 得到 6 。
步骤 3) 6 是偶数,除以 2 得到 3 。
步骤 4) 3 是奇数,减 1 得到 2 。
步骤 5) 2 是偶数,除以 2 得到 1 。
步骤 6) 1 是奇数,减 1 得到 0 。
示例 2:
输入:num = 8
输出:4
解释:
步骤 1) 8 是偶数,除以 2 得到 4 。
步骤 2) 4 是偶数,除以 2 得到 2 。
步骤 3) 2 是偶数,除以 2 得到 1 。
步骤 4) 1 是奇数,减 1 得到 0 。
示例 3:
输入:num = 123
输出:12
提示:
- 0 <= num <= 10^6
题解
我的解
这个题不难哈哈。
我的思路是直接弄个变量记录次数,循环判断是否是奇数偶数,进行操作。
class Solution {
public int numberOfSteps (int num) {
int flag = 0;
while (num > 0){
if (num % 2 == 0){
num = num / 2;
}else {
num = num -1;
}
flag++;
}
return flag;
}
}
官方解一:迭代
class Solution {
public int numberOfSteps (int num) {
int count = 0;
while (num != 0) {
count++;
num = (num & 1) == 1 ? num & -2 : num >> 1;
}
return count;
}
}
官方解二:递归
class Solution {
private int count = 0;
public int numberOfSteps (int num) {
if (num != 0) {
count++;
if ((num & 1) != 0) {
numberOfSteps(num & -2);
} else {
numberOfSteps(num >> 1);
}
}
return count;
}
}
总结
官方用了性能更优的位运算来判断是否为奇偶。
以32位二进制为例, 例如15(0x0000000f):
遇奇减1, 即将末位1变为0, 和0xfffffffe(-2)取&即可;
遇偶除2, 即右移一位即可;
两种情况都需要次数加1.
位运算咱不懂呀。。。。
10. 按既定顺序创建目标数组
题目
给你两个整数数组 nums 和 index。你需要按照以下规则创建目标数组:
- 目标数组 target 最初为空。
- 按从左到右的顺序依次读取 nums[i] 和 index[i],在 target 数组中的下标 index[i] 处插入值 nums[i] 。
- 重复上一步,直到在 nums 和 index 中都没有要读取的元素。
- 请你返回目标数组。
题目保证数字插入位置总是存在。
示例 1:
输入:nums = [0,1,2,3,4], index = [0,1,2,2,1]
输出:[0,4,1,3,2]
解释:
nums index target
0 0 [0]
1 1 [0,1]
2 2 [0,1,2]
3 2 [0,1,3,2]
4 1 [0,4,1,3,2]
示例 2:
输入:nums = [1,2,3,4,0], index = [0,1,2,3,0]
输出:[0,1,2,3,4]
解释:
nums index target
1 0 [1]
2 1 [1,2]
3 2 [1,2,3]
4 3 [1,2,3,4]
0 0 [0,1,2,3,4]
示例 3:
输入:nums = [1], index = [0]
输出:[1]
提示:
- 1 <= nums.length, index.length <= 100
- nums.length == index.length
- 0 <= nums[i] <= 100
- 0 <= index[i] <= i
题解
我的解
我首先想到的就是链表,链表的特性就是插入删除很方便。
class Solution {
public int[] createTargetArray(int[] nums, int[] index) {
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i < nums.length; i++) {
list.add(index[i],nums[i]);
}
for (int i = 0; i < nums.length; i++) {
nums[i] = list.get(i);
}
return nums;
}
}
官方解:请看我的解!
官方跟我的很像,只不过是用的 arraylist,没用 linkedlist。
总结
哦吼!
数据库题
1. 删除重复的电子邮箱
题目
编写一个 SQL 查询,来删除 Person 表中所有重复的电子邮箱,重复的邮箱里只保留 Id 最小 的那个。
Id | |
---|---|
1 | john@example.com |
2 | bob@example.com |
3 | john@example.com |
Id 是这个表的主键。
例如,在运行你的查询语句之后,上面的 Person 表应返回以下几行:
Id | |
---|---|
1 | john@example.com |
2 | bob@example.com |
提示:
- 执行 SQL 之后,输出是整个 Person 表。
- 使用 delete 语句。
题解
我的解
我的思路是找到数量大于 1 的邮箱,然后删除出了最小id的那一个。但是不会写 sql。
官方解:使用 DELETE 和 WHERE
我们可以使用以下代码,将此表与它自身在电子邮箱列中连接起来。
SELECT p1.*
FROM Person p1,
Person p2
WHERE
p1.Email = p2.Email
;
然后我们需要找到其他记录中具有相同电子邮件地址的更大 ID。所以我们可以像这样给 WHERE 子句添加一个新的条件。
SELECT p1.*
FROM Person p1,
Person p2
WHERE
p1.Email = p2.Email AND p1.Id > p2.Id
;
因为我们已经得到了要删除的记录,所以我们最终可以将该语句更改为 DELETE。
DELETE p1 FROM Person p1,
Person p2
WHERE
p1.Email = p2.Email AND p1.Id > p2.Id
总结
奇奇怪怪的,意思是只可能有两个一样的邮箱,不可能三个同时一样呗。
2. 重新格式化部门表
题目
部门表 Department:
Column Name | Type |
---|---|
id | int |
revenue | int |
month | varchar |
- (id, month) 是表的联合主键。
- 这个表格有关于每个部门每月收入的信息。
- 月份(month)可以取下列值 ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]。
编写一个 SQL 查询来重新格式化表,使得新的表中有一个部门 id 列和一些对应 每个月 的收入(revenue)列。
查询结果格式如下面的示例所示:
Department 表:
id | revenue | month |
---|---|---|
1 | 8000 | Jan |
2 | 9000 | Jan |
3 | 10000 | Feb |
1 | 7000 | Feb |
1 | 6000 | Mar |
查询得到的结果表:
id | Jan_Revenue | Feb_Revenue | Mar_Revenue | ... | Dec_Revenue |
---|---|---|---|---|---|
1 | 8000 | 7000 | 6000 | ... | null |
2 | 9000 | null | null | ... | null |
3 | null | 10000 | null | ... | null |
注意,结果表有 13 列 (1个部门 id 列 + 12个月份的收入列)。
题解
我的解
俺不会!
官方解一:分组 + MIN函数
(id, month) 是联合主键 故分组后,每个分组中的月份都是唯一的
聚合函数 使用MIN 或 MAX都行
SELECT
id,
MIN(IF(`month` = 'Jan', revenue, NULL)) AS Jan_Revenue,
MIN(IF(`month` = 'Feb', revenue, NULL)) AS Feb_Revenue,
MIN(IF(`month` = 'Mar', revenue, NULL)) AS Mar_Revenue,
MIN(IF(`month` = 'Apr', revenue, NULL)) AS Apr_Revenue,
MIN(IF(`month` = 'May', revenue, NULL)) AS May_Revenue,
MIN(IF(`month` = 'Jun', revenue, NULL)) AS Jun_Revenue,
MIN(IF(`month` = 'Jul', revenue, NULL)) AS Jul_Revenue,
MIN(IF(`month` = 'Aug', revenue, NULL)) AS Aug_Revenue,
MIN(IF(`month` = 'Sep', revenue, NULL)) AS Sep_Revenue,
MIN(IF(`month` = 'Oct', revenue, NULL)) AS Oct_Revenue,
MIN(IF(`month` = 'Nov', revenue, NULL)) AS Nov_Revenue,
MIN(IF(`month` = 'Dec', revenue, NULL)) AS Dec_Revenue
FROM
Department
GROUP BY id;
官方解二:分组 + sum函数
select
id
, sum(case `month` when 'Jan' then revenue else null end) as Jan_Revenue
, sum(case `month` when 'Feb' then revenue else null end) as Feb_Revenue
, sum(case `month` when 'Mar' then revenue else null end) as Mar_Revenue
, sum(case `month` when 'Apr' then revenue else null end) as Apr_Revenue
, sum(case `month` when 'May' then revenue else null end) as May_Revenue
, sum(case `month` when 'Jun' then revenue else null end) as Jun_Revenue
, sum(case `month` when 'Jul' then revenue else null end) as Jul_Revenue
, sum(case `month` when 'Aug' then revenue else null end) as Aug_Revenue
, sum(case `month` when 'Sep' then revenue else null end) as Sep_Revenue
, sum(case `month` when 'Oct' then revenue else null end) as Oct_Revenue
, sum(case `month` when 'Nov' then revenue else null end) as Nov_Revenue
, sum(case `month` when 'Dec' then revenue else null end) as Dec_Revenue
from Department group by id;
总结
这个题好难,着实不会,不过可以在向项目需求是上述类似时,应用到项目中。
3. 上升的温度
题目
给定一个 Weather 表,编写一个 SQL 查询,来查找与之前(昨天的)日期相比温度更高的所有日期的 Id。
Id(INT) | RecordDate(DATE) | Temperature(INT) |
---|---|---|
1 | 2015-01-01 | 10 |
2 | 2015-01-02 | 25 |
3 | 2015-01-03 | 20 |
4 | 2015-01-04 | 30 |
例如,根据上述给定的 Weather 表格,返回如下 Id:
Id |
---|
2 |
4 |
题解
我的解
我的思路是比较日期,然后比较温度。
可惜不会写 sql!下边代码不对。
select w1.id
from
Weather w1,Weather w2
where
w1.RecordDate > date_sub(w2.RecordDate,interval 1 day)
and w1.Temperature > w2.Temperature
官方解:JOIN 和 DATEDIFF()
使用 DATEDIFF 来比较两个日期类型的值。
因此,我们可以通过将 weather 与自身相结合,并使用 DATEDIFF() 函数。
SELECT
weather.id AS 'Id'
FROM
weather
JOIN
weather w ON DATEDIFF(weather.RecordDate, w.RecordDate) = 1
AND weather.Temperature > w.Temperature
;
总结
sql 还是菜啊我。
- jion:内连接,后跟 on 条件,查询出来的是两个表根据条件连接出来的数据。
- DATEDIFF(start,end):日期相差函数,start 与 end 两个日期之间相差的天数。
4. 第二高的薪水
题目
编写一个 SQL 查询,获取 Employee 表中第二高的薪水(Salary) 。
Id | Salary |
---|---|
1 | 100 |
2 | 200 |
3 | 300 |
例如上述 Employee 表,SQL查询应该返回 200 作为第二高的薪水。如果不存在第二高的薪水,那么查询应返回 null。
SecondHighestSalary |
---|
200 |
题解
我的解
这么说吧,俺不会。
官方解一:使用子查询和 LIMIT
将不同的薪资按降序排序,然后使用 LIMIT 子句获得第二高的薪资。
SELECT DISTINCT
Salary AS SecondHighestSalary
FROM
Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1
然而,如果没有这样的第二最高工资,这个解决方案将被判断为 “错误答案”,因为本表可能只有一项记录。为了克服这个问题,我们可以将其作为临时表。
SELECT
(SELECT DISTINCT
Salary
FROM
Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1) AS SecondHighestSalary
;
官方解二:使用 IFNULL 和 LIMIT
解决 “NULL” 问题的另一种方法是使用 “IFNULL” 函数,如下所示。
SELECT
IFNULL(
(SELECT DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1),
NULL) AS SecondHighestSalary
总结
以前没觉得 sql 有多难,现在觉得 sql 好难啊!!!
5. 换座位
题目
小美是一所中学的信息科技老师,她有一张 seat 座位表,平时用来储存学生名字和与他们相对应的座位 id。
其中纵列的 id 是连续递增的
小美想改变相邻俩学生的座位。
你能不能帮她写一个 SQL query 来输出小美想要的结果呢?
示例:
id | student |
---|---|
1 | Abbot |
2 | Doris |
3 | Emerson |
4 | Green |
5 | Jeames |
假如数据输入的是上表,则输出结果如下:
id | student |
---|---|
1 | Doris |
2 | Abbot |
3 | Green |
4 | Emerson |
5 | Jeames |
注意:
- 如果学生人数是奇数,则不需要改变最后一个同学的座位。
题解
我的解
不会。
官方解一:使用 CASE
对于所有座位 id 是奇数的学生,修改其 id 为 id+1,如果最后一个座位 id 也是奇数,则最后一个座位 id 不修改。对于所有座位 id 是偶数的学生,修改其 id 为 id-1。
首先查询座位的数量。
SELECT
COUNT(*) AS counts
FROM
seat
然后使用 CASE 条件和 MOD 函数修改每个学生的座位 id。
SELECT
(CASE
WHEN MOD(id, 2) != 0 AND counts != id THEN id + 1
WHEN MOD(id, 2) != 0 AND counts = id THEN id
ELSE id - 1
END) AS id,
student
FROM
seat,
(SELECT
COUNT(*) AS counts
FROM
seat) AS seat_counts
ORDER BY id ASC;
官方解二:使用位操作和 COALESCE()
使用 (id+1)^1-1 计算交换后每个学生的座位 id。
SELECT id, (id+1)^1-1, student FROM seat;
id | (id+1)^1-1 | student |
---|---|---|
1 | 2 | Abbot |
2 | 1 | Doris |
3 | 4 | Emerson |
4 | 3 | Green |
5 | 6 | Jeames |
然后连接原来的座位表和更新 id 后的座位表。
SELECT
*
FROM
seat s1
LEFT JOIN
seat s2 ON (s1.id+1)^1-1 = s2.id
ORDER BY s1.id;
id | student | id | student |
---|---|---|---|
1 | Abbot | 2 | Doris |
2 | Doris | 1 | Abbot |
3 | Emerson | 4 | Green |
4 | Green | 3 | Emerson |
5 | Jeames |
总结
自己做不会,看答案非常爽!