算法多线程leetcode题目总结(多解法实现)

2023-05-16

简介

本文汇总了leetcode上多线程题目,并对每一道题进行多方法解答,并分析不同方法之间的优劣。文中示例代码为Java。

题目

  • 1114. 按序打印 简单
  • 1115. 交替打印FooBar 中等
  • 1116. 打印零与奇偶数 中等
  • 1117. H2O 生成 中等
  • 1188. 设计有限阻塞队列
  • 1195. 交替打印字符串 中等
  • 1126. 哲学家进餐

以上题目均来自leetcode.

解答

1114. 按序打印

题目描述

我们提供了一个类:

public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}

三个不同的线程 A、B、C 将会共用一个 Foo 实例。

  • 一个将会调用 first() 方法
  • 一个将会调用 second() 方法
  • 还有一个将会调用 third() 方法

请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。

示例 1:

输入: [1,2,3]
输出: “firstsecondthird”
解释:
有三个线程会被异步启动。
输入 [1,2,3] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 second() 方法,线程 C 将会调用 third() 方法。
正确的输出是 “firstsecondthird”。

分析

  1. synchronized。三个同步块,每个同步块任务完成后通知其他同步块。
  2. 原子变量+轮询。用一个原子变量来判断是否满足执行任务的条件。
  3. 信号量。

代码

  • 方法一 synchronized
class Foo {

    public Foo() {
    }

    private volatile int state = 0;
    private final Object lock = new Object();

    public void first(Runnable printFirst) throws InterruptedException {
        synchronized (lock) {
            while (state != 0) {
                lock.wait();
            }
            printFirst.run();
            state = 1;
            lock.notifyAll();
        }
    }

    public void second(Runnable printSecond) throws InterruptedException {
        synchronized (lock) {
            while (state != 1) {
                lock.wait();
            }
            printSecond.run();
            state = 2;
            lock.notifyAll();
        }

    }

    public void third(Runnable printThird) throws InterruptedException {
        synchronized (lock) {
            while (state != 2) {
                lock.wait();
            }
            printThird.run();
            lock.notifyAll();
        }
    }
}

注意:

  1. 按理说first方法第一个开始执行,就不用进入同步块了,但是为了执行 notifyAll() 方法,就必须在同步块中,所以也无奈建立同步块。
  2. 注意使用 notifyAll(),而不是 notify() 方法。因为使用 notify() 只是唤醒一个线程,无法保证 first 方法里面唤醒的就是 second 线程。所以干脆使用 notifyAll(),唤醒所有正在等待池中线程,然后让他们去竞争锁。由于设置了state条件,所以只有一个线程能顺利执行逻辑,而不满足条件的再次进入等待池。
  3. 使用 synchronized 关键字,便会存在线程的上下文切换问题,开销比较大。
  • 方法二 原子变量+轮询
class Foo {

    public Foo() {
    }

    private AtomicInteger count = new AtomicInteger(0);

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        count.getAndIncrement();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        while (count.get() != 1) {
        }
        printSecond.run();
        count.getAndIncrement();
    }

    public void third(Runnable printThird) throws InterruptedException {
        while (count.get() != 2) {
        }
        printThird.run();
        count.getAndIncrement();
    }
}

注意:

  1. second() 和 third() 方法采用轮询检查的方式,判断条件是否满足。
  2. 轮询相比方法一避免了线程的上下文切换,但是存在CPU空转的问题。因此当轮询时间比较短的情况下,使用轮询的开销小于方法一;但是当轮询时间太长,白白浪费了CPU资源,开销比方法一更大。
  • 方法三 信号量
class Foo {

    public Foo() {
    }

    private Semaphore s1 = new Semaphore(0);
    private Semaphore s2 = new Semaphore(0);

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        s1.release();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        s1.acquire();
        printSecond.run();
        s2.release();
    }

    public void third(Runnable printThird) throws InterruptedException {
        s2.acquire();
        printThird.run();
        s2.release();
    }
}

注意:

  1. 信号量的实现方式比较优雅,使用两个信号量,代码简洁。

1115. 交替打印FooBar

题目描述

我们提供一个类:

class FooBar {
  public void foo() {
    for (int i = 0; i < n; i++) {
      print("foo");
    }
  }
  
  public void bar() {
    for (int i = 0; i < n; i++) {
      print("bar");
    }
  }
}

两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo() 方法,另一个线程将会调用 bar() 方法。

请设计修改程序,以确保 “foobar” 被输出 n 次。

示例 1:

输入: n = 1
输出: “foobar”
解释: 这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法,“foobar” 将被输出一次。

分析

  1. 信号量。这道题同样适用使用信号量的方法。

代码

class FooBar {
    private int n;
    private Semaphore s1 = new Semaphore(1);
    private Semaphore s2 = new Semaphore(0);


    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {

        for (int i = 0; i < n; i++) {
            s1.acquire();
            printFoo.run();
            s2.release();
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {

        for (int i = 0; i < n; i++) {
            s2.acquire();
            printBar.run();
            s1.release();
        }
    }
}

1116. 打印零与奇偶数

题目描述

假设有这么一个类:

class ZeroEvenOdd {
  public ZeroEvenOdd(int n) { ... }      // 构造函数
  public void zero(printNumber) { ... }  // 仅打印出 0
  public void even(printNumber) { ... }  // 仅打印出 偶数
  public void odd(printNumber) { ... }   // 仅打印出 奇数
}

相同的一个 ZeroEvenOdd 类实例将会传递给三个不同的线程:

线程 A 将调用 zero(),它只输出 0 。
线程 B 将调用 even(),它只输出偶数。
线程 C 将调用 odd(),它只输出奇数。
每个线程都有一个 printNumber 方法来输出一个整数。请修改给出的代码以输出整数序列 010203040506… ,其中序列的长度必须为 2n。

示例 1:

输入:n = 2
输出:“0102”
说明:三条线程异步执行,其中一个调用 zero(),另一个线程调用 even(),最后一个线程调用odd()。正确的输出为 “0102”。

分析

  1. 信号量。

代码

class ZeroEvenOdd {
    private int n;

    public ZeroEvenOdd(int n) {
        this.n = n;
    }

    private Semaphore s0 = new Semaphore(1);
    private Semaphore s1 = new Semaphore(0);
    private Semaphore s2 = new Semaphore(0);

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void zero(IntConsumer printNumber) throws InterruptedException {
        for (int i = 1; i <= n; i++) {
            s0.acquire();
            printNumber.accept(0);
            if (i % 2 == 1) {
                s1.release();
            } else {
                s2.release();
            }
        }
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        for (int i = 2; i <= n; i += 2) {
            s2.acquire();
            printNumber.accept(i);
            s0.release();
        }
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        for (int i = 1; i <= n; i += 2) {
            s1.acquire();
            printNumber.accept(i);
            s0.release();
        }
    }
}

1117. H2O 生成

题目描述

现在有两种线程,氧 oxygen 和氢 hydrogen,你的目标是组织这两种线程来产生水分子。

存在一个屏障(barrier)使得每个线程必须等候直到一个完整水分子能够被产生出来。

氢和氧线程会被分别给予 releaseHydrogenreleaseOxygen 方法来允许它们突破屏障。

这些线程应该三三成组突破屏障并能立即组合产生一个水分子。

你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。

换句话说:

如果一个氧线程到达屏障时没有氢线程到达,它必须等候直到两个氢线程到达。
如果一个氢线程到达屏障时没有其它线程到达,它必须等候直到一个氧线程和另一个氢线程到达。
书写满足这些限制条件的氢、氧线程同步代码。

示例 1:

输入: “HOH”
输出: “HHO”
解释: “HOH” 和 “OHH” 依然都是有效解。

分析

  1. 信号量。
    每完成一个“H”,释放一个“O”的信号,而完成“O”需要一次获取两个信号。

代码

class H2O {

    public H2O() {
    }

    Semaphore h = new Semaphore(2);
    Semaphore o = new Semaphore(0);

    public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
        h.acquire();
        // releaseHydrogen.run() outputs "H". Do not change or remove this line.
        releaseHydrogen.run();
        o.release();
    }

    public void oxygen(Runnable releaseOxygen) throws InterruptedException {
        o.acquire(2);
        // releaseOxygen.run() outputs "O". Do not change or remove this line.
        releaseOxygen.run();
        h.release(2);
    }
}

1195. 交替打印字符串

题目描述

编写一个可以从 1 到 n 输出代表这个数字的字符串的程序,但是:

如果这个数字可以被 3 整除,输出 “fizz”。
如果这个数字可以被 5 整除,输出 “buzz”。
如果这个数字可以同时被 3 和 5 整除,输出 “fizzbuzz”。
例如,当 n = 15,输出: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz。

假设有这么一个类:

class FizzBuzz {
  public FizzBuzz(int n) { ... }               // constructor
  public void fizz(printFizz) { ... }          // only output "fizz"
  public void buzz(printBuzz) { ... }          // only output "buzz"
  public void fizzbuzz(printFizzBuzz) { ... }  // only output "fizzbuzz"
  public void number(printNumber) { ... }      // only output the numbers
}

请你实现一个有四个线程的多线程版 FizzBuzz, 同一个 FizzBuzz 实例会被如下四个线程使用:

  1. 线程A将调用 fizz() 来判断是否能被 3 整除,如果可以,则输出 fizz。
  2. 线程B将调用 buzz() 来判断是否能被 5 整除,如果可以,则输出 buzz。
  3. 线程C将调用 fizzbuzz() 来判断是否同时能被 3 和 5 整除,如果可以,则输出 fizzbuzz。
  4. 线程D将调用 number() 来实现输出既不能被 3 整除也不能被 5 整除的数字。

提示:

  • 本题已经提供了打印字符串的相关方法,如 printFizz() 等,具体方法名请参考答题模板中的注释部分。

分析

  1. 信号量。

代码

class FizzBuzz {
    private int n;

    public FizzBuzz(int n) {
        this.n = n;
    }

    Semaphore s3 = new Semaphore(0);
    Semaphore s5 = new Semaphore(0);
    Semaphore s35 = new Semaphore(0);
    Semaphore sNum = new Semaphore(1);

    // printFizz.run() outputs "fizz".
    public void fizz(Runnable printFizz) throws InterruptedException {
        for (int i = 3; i <= n; i += 3) {
            if (i % 5 == 0) continue;
            s3.acquire();
            printFizz.run();
            sNum.release();
        }
    }

    // printBuzz.run() outputs "buzz".
    public void buzz(Runnable printBuzz) throws InterruptedException {
        for (int i = 5; i <= n; i += 5) {
            if (i % 3 == 0) continue;
            s5.acquire();
            printBuzz.run();
            sNum.release();
        }
    }

    // printFizzBuzz.run() outputs "fizzbuzz".
    public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
        for (int i = 15; i <= n; i += 15) {
            s35.acquire();
            printFizzBuzz.run();
            sNum.release();
        }
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void number(IntConsumer printNumber) throws InterruptedException {
        for (int i = 1; i <= n; i++) {
            sNum.acquire();
            if (i % 3 == 0 && i % 5 == 0) {
                s35.release();
            } else if (i % 3 == 0) {
                s3.release();
            } else if (i % 5 == 0) {
                s5.release();
            } else {
                printNumber.accept(i);
                sNum.release();
            }
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法多线程leetcode题目总结(多解法实现) 的相关文章

  • 解决 https 无法访问

    本人腾讯云服务器 xff0c 放假上班打开宝塔面板 xff0c 结果无法访问 我想着重启实例 xff0c 结果面板可以打开 xff0c 但是域名打不开了 xff08 之前是可以打开的 xff09 最后在这个地址找到了答案 xff0c 记录一
  • Python程序设计 简单的图像处理(1)

    Python程序设计 简单的图像处理 xff08 1 xff09 1 写个滤镜 照片照的好 xff0c 不如滤镜用得好 xff01 一款好的滤镜软件可以让照片呈现不一样的风格乃至风情 xff0c 修理照片需要扬长避短达到最佳效果 可是滤镜款
  • Xcode操作流

    1 Xcode IDE概览 说明 xff1a 从左到右 xff0c 依次是 导航窗格 xff08 Navigator xff09 gt 边列 xff08 Gutter xff09 gt 焦点列 xff08 Ribbon xff09 gt 代
  • java gui 多线程,界面假死、僵死问题

    xff08 转载1 xff09 楼主bluepb xff08 流星 xff09 2005 06 04 20 28 17 在 Java GUI 设计 提问 我现在在用jAVA做图形化设计 xff0c 想问个多线程的问题 比如在一个窗口上点个按
  • excel数据对比-----查找两列(表)的相同数据

    原创作品 xff0c 允许转载 xff0c 转载时请务必以超链接形式标明文章 原始出处 作者信息和本声明 否则将追究法律责任 http xueli blog 51cto com 3325186 920592 现有两个excel表 xff0c
  • discuz 微社区 您请求的XXXX无法访问 接口错误(ERR02)

    我遇到的情况 xff1a 1 UC可以访问页面 xff0c 用微信报错 2 4G网络下可以访问 xff0c WiFi网络下报错 网上有两种解决方法 xff1a 1 关闭防采集 xff0c 我最终的采用方法 2 default下的mobile
  • 所有文件夹都变成1KB文件夹快捷方式病毒的手动清除方法

    电脑差不多都因使用U盘而感染了病毒 xff0c 其中一个就是Autoran病毒的变种 xff0c 它的症状我就不再描述了 xff0c 另外一个病毒的症状是所有文件夹都变成了1KB文件夹快捷方式 xff0c 各盘无法双击打开 xff08 但右
  • 搜狗高速浏览器2.0使用体验

    2010年 4 月 8 号 xff0c 我们终于迎来了 国内浏览器的后起之秀搜狗高速浏览器2 0 正式版 的 发布 高速真双核引擎 的概念得到了落实 它新增并改进了诸多功能 xff0c 修改了一些bug xff0c 从整体提高 搜狗高速浏览
  • Connection refused错误

    这个问题整了我两天时间 xff0c 现在终于解决了 问题 xff1a 用php 构造http请求访问自身web服务器页面 xff0c 总是报Connection refused 111 错误 显示 xff1a unable to conne
  • QT样式表从入门到精通

    QT样式表从入门到精通 文章目录 QT样式表从入门到精通前言1 背景介绍2 初级学习2 1 34 盒子 34 模型2 2 语法说明2 3 基础控件2 4 控件状态表2 5 选择器 3 中级学习3 1 坐标讲解3 1 1 相对坐标3 1 2
  • GIF89a图片头文件欺骗

    1 什么是GIF89a 一个GIF89a图形文件就是一个根据图形交换格式 xff08 GIF xff09 89a版 xff08 1989年7 月发行 xff09 进行格式化之后的图形 在GIF89a之前还有87a版 xff08 1987年5
  • txt文件导入mysql

    LOAD DATA LOW PRIORITY CONCURRENT LOCAL INFILE 39 file name 39 REPLACE IGNORE INTO TABLE tbl name CHARACTER SET charset
  • mac下终端无法使用数字小键盘的解决方案

    终端下 偏好设置 xff0d 高级 xff0d xff08 去掉 xff09 允许VT100应用程序小键盘模式
  • Mac Eclipse Failed to load JavaHL Library.

    转自 xff1a http blog csdn net wy10207010219 article details 42294293 写这一篇前我想发表一下感慨 xff1a 你所害怕的事 xff0c 你想要逃避的事 xff0c 在将来的某个
  • ROS学习笔记(一)ROS安装和helloworld

    ROS学习笔记 xff08 一 xff09 ROS安装和helloworld 文章目录 一 ros安装及测试1 打开ubuntu软件和更新 xff0c 进行如下设置2 设置安装源3 设置安装密钥4 更新软件源5 安装ros6 添加命令7 初
  • 使用ActiveMQ进行C++与C#的通信4 - 使用C++连接ActiveMQ

    在上一节编译ActiveMQ CPP的基础上 xff0c 创建C 43 43 控制台应用程序 xff0c 将activemq cpp项目中的include文件夹拷贝到该C 43 43 项目中 xff0c 设置好附加包含目录 将生成好的lib
  • 使用ActiveMQ进行C++与C#的通信5 - 实现C++和C#的通信

    在前几篇文章分别实现C C 43 43 连接ActiveMQ的基础上 xff0c 本文介绍如何使它们通信 使不同的进程对同一个ActiveMQ消息队列进行访问 xff0c 就能够达到消息互通的效果 例如使用queue test1 log作为
  • 【计算机游戏开发】游戏交互界面设计

    github项目地址 一 实验目的与要求 熟悉交互界面设计原理 了解Cocos2d x中的用户交互 触摸事件 碰撞检测机制 二 实验内容与方法 完成游戏编译 50分 仿照实验一 英雄快跑 实验 xff0c 将教材源码和素材文件复制到自己的项
  • k-近邻实现手写数字识别

    1 k 近邻工作原理 简单地说 xff0c K近邻算法采用测量不同特征值之间的距离方法进行分类 该算法具有一下特点 优点 xff1a 精度高 对异常值不敏感 无数据输入假定 缺点 xff1a 计算复杂度高 空间复杂度高 K近邻算法的工作原理
  • selenium之CSS定位

    一 层级定位 1 xff1a 所有标签 2 标签名 xff1a 查找所有该标签名 3 标签名 xff0c 标签名 xff1a 查找多个标签名 id用 表示 索引尽量使用xpath 二 三大等待和切换 1 页面元素可以定位 xff0c 但是代

随机推荐