手撕题

手撕题

单例模式

什么是单例模式

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象

aHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlLzAvMjAyMC9wbmcvMTY5NDAyOS8xNTk0NTE1MTI2Njk4LTg5NmU2NDQzLTBmOWUtNGM4Yy1iNGEwLWJiMDRjOWUxY2I2ZC5wbmc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private volatile static Singleton singleton;

public Singleton() {
}

public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
}

Double Check(双重校验) + Lock(加锁)

  1. 使用volatile防止指令重排
    创建一个对象,在JVM中会经过三步:

(1)为singleton分配内存空间

(2)初始化singleton对象

(3)将singleton指向分配好的内存空间(执行完这一步代表已经singleton不为空)

指令重排序是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能

在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报NPE异常

aHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlLzAvMjAyMC9wbmcvMTY5NDAyOS8xNTk0NTIyMzkwNzk3LTRkZjBkMDA4LTM3MmMtNDkxZi04YjlhLWY4NjBmODAzNzFhYi5wbmc

队列

数组

入队O(1),出队O(n)

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
public class Queue<T> {
private T[] arr;
// private int head;
private int tail;
public Queue(int size) {
arr = (T[]) new Object[size];
}
// 入队
public void enqueue(T t) {
arr[tail++] = t;
}
// 出队
public T dequeue() {
if(tail == 0) {
return null;
}
T t = arr[0];
//首位后面的元素开始都向前移动一位
for(int i = 1; i < tail; i++) {
arr[i-1] = arr[i];
}
arr[tail--] = null;
return t;
}

public static void main(String[] args) {
Queue<Integer> queue = new Queue<>(10);
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.enqueue(4);
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
}
}

链表

入队O(1),出队O(1)

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
39
40
41
42
43
44
45
46
47
public class LinkedQueue<T> {

private Node<T> head;
private Node<T> tail;
private int size;
public LinkedQueue() {
head = null;
tail = null;
size = 0;
}

// 入队
public void enqueue(T t) {
Node<T> node = new Node<>(t);
if (size == 0){
tail = node;
head = node;
size++;
return;
}
size++;
head.pre = node;
node.next = head;
head = node;
}

// 出队
public T dequeue() {
if (tail == null){
return null;
}
size--;
T val = tail.val;
tail = tail.pre;
return val;
}
private static class Node<T> {
T val;
Node<T> next;
Node<T> pre;
public Node(T val) {
this.val = val;
}
}


}

循环链表

  • head
  • tail
  • size

https://blog.csdn.net/wkd_007/article/details/129824129

e8d74047d305a0e2972f64b79bfeabce

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
39
40
41
42
43
44
public class LoopList<V> {

Node<V> head;
Node<V> tail;
int size;
public LoopList(){
//初始化
tail = head = null;
size = 0;
}

void insert(Node<V> n){
if (size==0){
n.next = n;
head = n;
tail = n;
size++;
return;
}
n.next = head;
head = n;
tail.next = head;
size++;
}

// 打印链表
void print(){
Node<V> n = head;
while (n.next != head){
System.out.println(n.val);
n=n.next;
}
System.out.println(tail.val);
}


private static class Node<V> {
V val;
Node<V> next;
public Node(V val) {
this.val = val;
}
}
}

手写LRU

  • capacity:缓存长度
  • map:保障取数据的时间复杂度为O(1)
  • head:对头节点变更频繁(insert、get
  • tail:长度超标时删除尾结点要用(insert

为什么要使用双向链表呢?

  • 双向队列用于维护活跃的数据
  • 当对一个元素进行更新操作时需要移动,移动需要对next以及pre进行连接,维护链表的线性结构,而单链表无法快捷找到pre的节点

image-20250314102554708

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
* @Projectname: HandTornQuestions
* @Filename: LRU
* @Author: FANSEA
* @Date:2024/10/28 19:41
*/
public class LRUCache<K,V> {
private final int capacity;
private Node<V> head;
private Node<V> tail;
// private K tailKey;
private final Map<K,Node<V>> map;

public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>(capacity);
}

// 插入元素
public void insert(K key,V value){
Node<V> node = new Node<>(value);
if (map.isEmpty()){
map.put(key,node);
head = node;
tail = node;
return;
}
// 判断当前key是否存在,如果存在则替换值
if (map.containsKey(key)){
map.put(key,node);
return;
}
// 如果空间大于设定值
if (map.size()==capacity){
// 移除最后一个元素
// map.remove(tailKey);
removeMapKey(tail.val);
tail = tail.pre;
tail.next = null;
}
node.next = head;
head.pre = node;
head = node;
map.put(key,node);
}

public V get(K key){
if (!map.containsKey(key)){
return null;
}
// 移动当前元素到最前面
Node<V> node = map.get(key);
if (node!=head){
if (node == tail){
tail = node.pre;
}
node.pre = node.next;
node.next = head;
head = node;
}
return node.val;
}


public void remove(K key){
if (!map.containsKey(key)){
return;
}
Node<V> x = map.get(key);
Node<V> pre = x.pre;
if (pre!=null){
pre.next = x.next;
}
map.remove(key);
}

private void removeMapKey(V value){
for (Map.Entry<K, Node<V>> entry : map.entrySet()){
if (entry.getValue().val.equals(value)){
map.remove(entry.getKey());
break;
}
}

}

private static class Node<V> {
V val;
Node<V> next;
Node<V> pre;
public Node(V val) {
this.val = val;
}
}

}

读写锁

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* ReadWriteLock
*/
public class ReadWriteLock {

private int readCount = 0;
private boolean isWrite = false;
private final Object monitor = new Object();

public void acquireReadLock(){
synchronized (monitor){
while (isWrite){
try {
monitor.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
readCount++;
}
}

public void unReadLock(){
synchronized (monitor){
if (!isWrite){
if (--readCount == 0){
monitor.notifyAll();
}
}
}
}

public void acquireWriteLock(){
synchronized (monitor){
while (readCount>0 || isWrite){
try {
monitor.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
isWrite = true;
}
}

public void unWriteLock(){
synchronized (monitor){
if (isWrite){
isWrite = false;
}
monitor.notifyAll();
}
}

}

自定义线程池

确定参数:

  • 核心线程数
  • 最大线程数
  • 是否关闭
  • 阻塞队列
  • 最大活跃时间(单位)
  • 工作线程数量

submit()方法:

  • 小于最大线程:加入任务
  • 大于最大线程:加入队列、拒绝任务

Work工作类:

循环

  • 如果有任务直接执行
  • 没有任务阻塞获取任务,如果超时判断是否工作线程大于核心线程,超过则释放当前线程
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public class ThreadPool {
private final int coreSize;
private final int maxSize;
private volatile boolean isShutdown = false;
private final long activeTime;
private final TimeUnit unit;
private final BlockingQueue<Runnable> taskQueue;
private final AtomicInteger workerCount = new AtomicInteger(0);

public ThreadPool(int coreSize, int maxSize, long activeTime,TimeUnit unit, int queueSize) {
this.coreSize = coreSize;
if (maxSize<coreSize){
throw new IllegalArgumentException("maxSize must be greater than coreSize");
}
this.maxSize = maxSize;
this.activeTime = activeTime;
this.taskQueue = new LinkedBlockingQueue<>(queueSize);
this.unit = unit;
}

private void submit(Runnable task){
//1.判断当前线程池中的线程数量是否小于核心线程数
if (task == null){
throw new NullPointerException("task is null");
}
synchronized (workerCount){
if (workerCount.get() < maxSize){
//1.1小于核心线程数,则创建线程执行任务
Worker worker = new Worker(task);
workerCount.incrementAndGet();
worker.start();
}else {
try {
taskQueue.add(task);
} catch (IllegalStateException e) {
System.err.println("taskQueue is full");
}
}
}
}

public void shutdown(){
isShutdown = true;
}

private class Worker extends Thread{
private Runnable task;
public Worker(Runnable task){
this.task = task;
}
@Override
public void run() {
while (!isShutdown){
if (task != null){
task.run();
task = null;
}else {
try {
task = taskQueue.poll(activeTime, unit);
if (task == null){
if (workerCount.get() > coreSize){
break;
}
continue;
}
task.run();
task = null;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
workerCount.decrementAndGet();
}
}

public static void main(String[] args) throws InterruptedException {
ThreadPool threadPool = new ThreadPool(5,9, 1, TimeUnit.SECONDS,10);
AtomicInteger j = new AtomicInteger();
for (int i = 0; i < 20; i++){
if (i==19){
sleep(100);
}
threadPool.submit(() ->
System.out.println("Thread"+(j.getAndIncrement()))
);
}
System.out.println(threadPool.workerCount.get());
threadPool.shutdown();
sleep(1000);
System.out.println(threadPool.workerCount.get());
}


}

螺旋矩阵

https://www.bilibili.com/video/BV1qp4y1h7cq/?spm_id_from=333.337.search-card.all.click&vd_source=70a5c913e74574ad96afc2ae210ba3e0

  • 定义上下左右四个指针
  • 通过固定一个指针,操作另外一个指针达到螺旋效果(→↓←↑)
  • 缩小所有指针一个单元的范围
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
private static int[][] spiralMatrix(int n){
int top = 0;
int bottom = n-1;
int left = 0;
int right = n-1;
int cur = 1;
int[][] arr = new int[n][n];
while (top<=bottom && left<=right){
for (int i=left;i<right-left;i++){
arr[top][i] = cur++;
}
for (int i=top;i<bottom-top;i++){
arr[i][right] = cur++;
}
for (int i=right;i>left;i--){
arr[bottom][i] = cur++;
}
for (int i=bottom;i>top;i--){
arr[i][left] = cur++;
}
if (top==bottom&&right==left){
arr[top][left] = n*n;
}
top++;bottom--;left++;right--;
}
return arr;
}

排序

归并排序

79510ba165786116c3a394cf6c4ee6f9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static int[] mergeSort(int[] arr,int l,int r){
if (l==r){
return new int[]{arr[l]};
}
int mid = (l+r)/2;
int[] leftList = mergeSort(arr,l,mid);
int[] rightList = mergeSort(arr,mid+1,r);
// 合并
int[] newList = new int[leftList.length+rightList.length];
int k =0;int i=0;int j=0;
while (i<leftList.length &&j<rightList.length){
newList[k++] = leftList[i] < rightList[j] ? leftList[i++] : rightList[j++];
}
while (i<leftList.length){
newList[k++] = leftList[i++];
}
while (j<rightList.length){
newList[k++] = rightList[j++];
}
return newList;
}

快速排序

https://www.bilibili.com/video/BV12e411e7cF/?spm_id_from=333.337.search-card.all.click&vd_source=70a5c913e74574ad96afc2ae210ba3e0

对整个数组进行排序,大于基准元素的在右边,小于的在左边,然后再对基准元素左右两边的数组递归排序

  • i:小于基准元素的空缺位指针
  • j:大于基准元素的空缺位指针
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
private static void quickSort(int[] arr, int i, int j) {
if (i<j){
int cur = arr[i];
int low = i;
int high = j;
while (i<j){
while((i<j) && cur<arr[j]){
j--;
}
// arr[j]比较小,需要移动到i
if (i<j){
arr[i] = arr[j];
i++;
}
while ((i<j) && arr[i]<cur){
i++;
}
// arr[i]比较大,需要移动到j
if (i<j){
arr[j] = arr[i];
j--;
}
}
arr[i] = cur;
quickSort(arr,low,i-1);
quickSort(arr,i+1,high);
}
}

递归

反转链表

1
2
3
4
5
6
7
8
9
10
11
12
13
public ListNode ReverseList (ListNode head) {
// write code here
return reverse(head,null);
}

public ListNode reverse(ListNode n,ListNode m){
if(n==null){
return m;
}
ListNode i = reverse(n.next,n);
n.next = m;
return i;
}

二叉树中 2 个节点的最先公共祖先

二叉树(反)序列化

序列化

1
2
3
4
5
6
7
8
9
10
11
public String serialize(TreeNode root) {
// write code here
if (root == null) {
return "null";
}
StringBuilder sb = new StringBuilder();
sb.append(root.val).append(",");
sb.append("("+serialize(root.left));
sb.append(serialize(root.right)+")");
return sb.toString();
}

反序列化

1

字符串

反转字符串II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public String reverseStr(String s, int k) {
char[] arr = s.toCharArray();
for(int i=0;i<s.length()-1;i+=2*k){
reverse(arr,i,Math.min(i+k,s.length())-1);
}
return new String(arr);
}
void reverse(char[] arr,int i,int j){
int l = i;int r = j;
while(l<r){
char temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;r--;
}
}

大数相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int al = a.length()-1;
int bl = b.length()-1;
int temp = 0;
StringBuilder s = new StringBuilder();
while (al>=0||bl>=0){
int i = func(a, al) + func(b, bl) + temp;
temp = i/10;
al--;
bl--;
s.append(i%10);
}
return s.reverse().toString();
}

static int func(String s,int i){
if (i<0){
return 0;
}
return Integer.parseInt(s.charAt(i)+"");
}

前序遍历

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
public static void preOrder(TreeNode n){
if (n==null){
return;
}
System.out.println(n.val);
preOrder(n.left);
preOrder(n.right);
}
// 栈:先压右边再压左
public static void preOrderPlus(TreeNode n){
System.out.println(n.val);
Stack<TreeNode> treeNodes = new Stack<>();
treeNodes.push(n.right);
treeNodes.push(n.left);
while (!treeNodes.isEmpty()){
TreeNode pop = treeNodes.pop();
System.out.println(pop.val);
if (pop.right != null){
treeNodes.push(pop.right);
}
if (pop.left != null){
treeNodes.push(pop.left);
}
}
}

中序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void midOrderPlus(TreeNode n){
Stack<TreeNode> treeNodes = new Stack<>();
TreeNode cur = n;
while (cur!=null || !treeNodes.isEmpty()){
while (cur!=null){
treeNodes.push(cur);
cur = cur.left;
}
TreeNode pop = treeNodes.pop();
System.out.println(pop.val);
cur = pop.right;

}
}

有序数组转化为平衡二叉树

二叉搜索树

  • 非空左子树的所有键值小于其根结点的键值。
  • 非空右子树的所有键值大于其根结点的键值。
  • 左、右子树都是二叉搜索树。
1
2
3
4
5
6
7
8
9
10
11
public TreeNode sort(int[] arr,int l,int r){

if (l > r) {
return null;
}
int mid = (r+l)/2;
TreeNode left = sort(arr,l,mid-1);
TreeNode right = sort(arr,mid+1,r);
return new TreeNode(arr[mid],left,right);

}

常用API

String

  1. toCharArray()

  2. indexOf(int ch)lastIndexOf(int ch) - 返回指定字符在此字符串中第一次出现或最后一次出现处的索引。

    1
    2
    int indexFirst = str.indexOf('l'); // 2
    int indexLast = str.lastIndexOf('l'); // 3
  3. substring(int beginIndex)substring(int beginIndex, int endIndex) - 返回一个新的字符串,它是此字符串的子串。

    1
    2
    String subStr = str.substring(1); // "ello"
    String subStrRange = str.substring(1, 4); // "ell"
  4. replace(char oldChar, char newChar) - 返回一个新的字符串,它是通过用新字符替换此字符串中出现的所有旧字符生成的。

    1
    String replacedStr = str.replace('l', 'p'); // "Heppo"
  5. toLowerCase()toUpperCase() - 将所有字符转换为小写或大写。

    1
    2
    String lowerCaseStr = str.toLowerCase(); // "hello"
    String upperCaseStr = str.toUpperCase(); // "HELLO"
  6. trim() - 返回一个去除首尾空格的新字符串。

    1
    2
    String withSpaces = " Hello ";
    String trimmed = withSpaces.trim(); // "Hello"
  7. split(String regex) - 根据给定的正则表达式拆分此字符串。

    1
    String[] words = "Hello World".split(" "); // ["Hello", "World"]

这些方法能够帮助你高效地处理字符串数据,无论是进行简单的字符串操作还是复杂的文本处理任务。根据具体需求选择合适的方法使用。

Map

在Java中,Map 接口和其实现类(如 HashMap, TreeMap, LinkedHashMap 等)提供了一种存储键值对的方式。以下是一些常用的 Map API 方法:

  1. remove(Object key) - 如果存在一个键的映射关系,则将其从此映射中移除。

    1
    map.remove("Apple");
  2. containsKey(Object key) - 如果此映射包含指定键的映射关系,则返回 true

    1
    boolean hasApple = map.containsKey("Apple");
  3. containsValue(Object value) - 如果此映射将一个或多个键映射到指定值,则返回 true

    1
    boolean hasValue = map.containsValue(1);
  4. keySet() - 返回此映射中包含的键的 Set 视图。

    1
    Set<String> keys = map.keySet();
  5. values() - 返回此映射中包含的值的 Collection 视图。

    1
    Collection<Integer> values = map.values();
  6. entrySet() - 返回此映射中包含的映射关系的 Set 视图。

    1
    2
    3
    4
    Set<Map.Entry<String, Integer>> entries = map.entrySet();
    for (Map.Entry<String, Integer> entry : entries) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
    }
  7. isEmpty() - 如果此映射未包含键-值映射关系,则返回 true

    1
    boolean empty = map.isEmpty();
  8. clear() - 从此映射中移除所有映射关系。

    1
    map.clear();
  9. size() - 返回此映射中的键-值映射关系数。

    1
    int size = map.size();
  10. putAll(Map<? extends K, ? extends V> m) - 从指定映射中将所有映射关系复制到此映射中。

    1
    2
    3
    Map<String, Integer> anotherMap = new HashMap<>();
    anotherMap.put("Banana", 2);
    map.putAll(anotherMap);

这些方法提供了强大的功能来管理键值对集合,适用于各种场景,比如数据缓存、配置信息存储等。选择合适的实现类(如 HashMap 提供快速查找但不保证顺序,而 LinkedHashMap 保持插入顺序,TreeMap 按键排序)可以进一步满足特定需求。

数组

构造器:

1
new String(arr) // 直接将数组转化为字符串

Arrays

在Java中,Arrays 类提供了一系列静态方法用于操作数组,如排序、搜索、比较等。以下是一些常用的 Arrays 方法:

  1. sort() - 对数组进行排序。

    1
    2
    int[] numbers = {3, 1, 2};
    Arrays.sort(numbers); // 排序后,numbers 数组变为 [1, 2, 3]
  2. binarySearch() - 使用二分查找算法查找指定元素的索引。注意,使用此方法前需要对数组进行排序。

    1
    int index = Arrays.binarySearch(numbers, 2); // 查找值为2的元素的位置
  3. equals() - 比较两个数组是否相等(元素数量和内容相同)。

    1
    boolean isEqual = Arrays.equals(array1, array2);
  4. toString() - 将数组转换为字符串表示形式。

    1
    String str = Arrays.toString(numbers); // 输出 "[1, 2, 3]"
  5. asList() - 将数组转为 List

    1
    List<String> list = Arrays.asList(new String[]{"a", "b", "c"});
  6. fill() - 用特定值填充数组。

    1
    Arrays.fill(numbers, 5); // 将 numbers 数组中的所有元素设置为 5
  7. copyOf() 和 copyOfRange() - 复制数组或数组的一部分。

    1
    2
    int[] newArray = Arrays.copyOf(numbers, 5); // 复制长度为5的新数组
    int[] rangeArray = Arrays.copyOfRange(numbers, 1, 3); // 复制从索引1到3(不包括3)的元素

这些方法可以大大简化数组的操作过程,使得代码更加简洁高效。请根据实际需求选择合适的方法使用。

SQL加油站

前置SQL

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
create table Student(sid varchar(10),sname varchar(10),sage datetime,ssex nvarchar(10));
insert into Student values('01' , '赵雷' , '1990-01-01' , '男');
insert into Student values('02' , '钱电' , '1990-12-21' , '男');
insert into Student values('03' , '孙风' , '1990-05-20' , '男');
insert into Student values('04' , '李云' , '1990-08-06' , '男');
insert into Student values('05' , '周梅' , '1991-12-01' , '女');
insert into Student values('06' , '吴兰' , '1992-03-01' , '女');
insert into Student values('07' , '郑竹' , '1989-07-01' , '女');
insert into Student values('08' , '王菊' , '1990-01-20' , '女');
create table Course(cid varchar(10),cname varchar(10),tid varchar(10));
insert into Course values('01' , '语文' , '02');
insert into Course values('02' , '数学' , '01');
insert into Course values('03' , '英语' , '03');
create table Teacher(tid varchar(10),tname varchar(10));
insert into Teacher values('01' , '张三');
insert into Teacher values('02' , '李四');
insert into Teacher values('03' , '王五');
create table SC(sid varchar(10),cid varchar(10),score decimal(18,1));
insert into SC values('01' , '01' , 80);
insert into SC values('01' , '02' , 90);
insert into SC values('01' , '03' , 99);
insert into SC values('02' , '01' , 70);
insert into SC values('02' , '02' , 60);
insert into SC values('02' , '03' , 80);
insert into SC values('03' , '01' , 80);
insert into SC values('03' , '02' , 80);
insert into SC values('03' , '03' , 80);
insert into SC values('04' , '01' , 50);
insert into SC values('04' , '02' , 30);
insert into SC values('04' , '03' , 20);
insert into SC values('05' , '01' , 76);
insert into SC values('05' , '02' , 87);
insert into SC values('06' , '01' , 31);
insert into SC values('06' , '03' , 34);
insert into SC values('07' , '02' , 89);
insert into SC values('07' , '03' , 98);
  1. 查询“01”课程比“02”课程成绩高的所有学生的学号?
1
2
3
SELECT t1.sid FROM (SELECT * FROM sc WHERE sc.cid = '01') t1
left JOIN (SELECT * FROM sc WHERE sc.cid = '02') t2
on t1.sid = t2.sid WHERE t1.score > t2.score;
  1. 查询平均成绩大于60分的同学的学号和平均成绩;
1
select sid,AVG(score) FROM sc GROUP BY sid HAVING AVG(score)>60
  1. 查询所有同学的学号、姓名、选课数、总成绩

请注意:使用GROUP BY 分组函数检索的非聚合函数属性都必须在GROUP BY条件之后!

1
2
3
4
5
6
7
8
9
10
SELECT
sc.sid,
s.sname,
COUNT(sc.sid) AS count_sid,
SUM(sc.score) AS total_score
FROM
sc
LEFT JOIN student s ON sc.sid = s.sid
GROUP BY
sc.sid, s.sname;
  1. 查询姓“李”的老师的个数;
1
select count(t.tid) from teacher t where t.tname like '李%';  
  1. 查询没学过“张三”老师课的同学的学号、姓名;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
select
sid,sname
from student
where sid not in
(
select
sc.sid
from teacher
left join course
on teacher.tid=course.tid
left join sc
on course.cid=sc.cid
where teacher.tname='张三'
)