《劍指Offer》62:圓圈中最后剩下的數字(約瑟夫環)

題目

0,1,2…,n-1這n個數字排成一個圓圈,從數字0開始,每次從這圓圈你刪除第m個數字。求出這個圓圈里剩下的最后一個數字。

例如,0、1、2、3、4這5個數字組成一個圓圈,從數字0開始每次刪除第3個數字,則刪除的前4個數字依次2、0、4、1,因此最后剩下的數字是3。

本題就是有名的約瑟夫(Josephuse)環問題。

分析

兩種解題方法:

  1. 用環形鏈表模擬圓圈的經典解法
  2. 分析每次被刪除的數字的規律并直接計算出圓圈中最后剩下的數字

放碼

用環形鏈表模擬圓圈的經典解法

public int lastRemaining(int n, int m) {if(n < 1 || m < 1) {throw new IllegalArgumentException();}LinkedList<Integer> list = new LinkedList<>();for(int i = 0; i < n; i++) {list.add(i);}int count = 0, index = 0;while(list.size() > 1) {count++;if(count == m) {list.remove(index);count = 0;}else {index++;}if(index == list.size()) {index = 0;}}return list.get(0);
}

分析每次被刪除的數字的規律并直接計算出圓圈中最后剩下的數字

首先我們定義一個關于 n 和 m 的方程f(n,m),表示每次在 n 個數字 0,1, … ,n-1中每次刪除第 m 個數字最后剩下的數字。

在這 n個數字中,第一個被刪除的數字是(m-1)%n。為了簡單起見,我們把(m- 1)%n 記為 k,那么刪除k之后剩下的 n-1 個數字為 0,1,… ,k-1,k+1,… ,n-1,并且下一次刪除從數字 k+1 開始計數。相當于在剩下的序列中, k+1 排在最前面,從而形成 k+1,… ,n- 1,0,1,… ,k-1 。

該序列最后剩下的數字也應該是關于 n 和 m 的函數。由于這個序列的規律和前面最初的序列不一樣(最初的序列是從 0 開始的連續序列),因此該函數不同于前面的函數,記為 f’(n-1,m)。

最初序列最后剩下的數字 f(n, m)一定是刪除一個數字之后的序列最后剩下的數字,即 f(n, m)=f'(n-1, m)

接下來我們把剩下的這 n-1 個數字的序列 k-1, …,n-1,0,1,… ,k-1 做一個映射,映射的結果是形成一個從 0 到 n-2 的序列:

last index->index
k+10
k+21
n-1n-k-2
0n-k-1
1n-k
k-1n-2

我們把映射定義為p,則p(x)=(x-k-1)%n if p(x)<0, then p(x)+=n

它表示如果映射前的數字是x,那么映射后的數字是(x-k-1)%n。該映射的逆映射是p?1(x)=(x+k+1)%n

由于映射之后的序列和最初的序列具有同樣的形式,即都是從0開始的連續序列,因此仍然可以用函數f來表示,記為f(n-1, m)。根據我們的映射規則,映射之前的序列中最后剩下的數字f'(n-1, m)=p?1[f(n-1, m)]=[f(n - 1, m) + k + 1] % n,把k = (m - 1) % n代入得到f(n, m)=f'(n-1, m)=[f(n-1, m) + m] % n

經過上面復雜的分析,我們終于找到了一個遞歸公式。要得到n個數字的序列中最后剩下的數字,只需要得到n-1個數字的序列中最后剩下的數字,并以此類推。當n=1時,也就是序列中開始只有一個數字0,那么很顯然最后剩下的數字就是0。我們把這種關系表示為:

public int lastRemaining2(int n, int m) {if(n < 1 || m < 1) {throw new IllegalArgumentException();}return n == 1 ? 0 : (lastRemaining2(n - 1, m) + m) % n;
}

n=5,m=3的推導過程

針對這個題目,先說說難點:

數字組成是環形的結構,當數到最后個數字時,還不是需要刪除的第 m 個數,需要回至數組的首位繼續;
每次重新數的位置,都是上次刪除數字的下一位。
針對第一個難點,可以考慮取模;

針對第二個難點,可以考慮將刪除數字下一位,作為下次重新數的起點,剩余數字依次排列。(注意數字組成是環狀的)

考慮先模擬,然后再進行逆推:

(為體現閉環,這里將數組進行復制。注意: 未得到最后 1 位數時,除第 1 輪開始 ,每一輪都是以上一輪刪除數字下一位作為起點,重新數需要刪除的第 m 個數)

這就是模擬之后得到的結果。

現在我們來進行逆推:

最終確定的 1 個數字,這個數字對應的索引一定是 0,逆推這個最終數字在每一輪中所處的索引位置,那么假設(n 表示數組元素個數,m 表示要刪除的第 m 個數,取示例 1,n = 5, m = 3):

  • n = 1 時,索引:0;
  • n = 2 時,索引:(0 + m) % 2 = 3 % 2 = 1;
  • n = 3 時,索引:((0 + m) % 2 + 3) % 3 = (1 + 3) % 3 = 1;
  • n = 4 時,索引:(((0 + m) % 2 + 3) % 3 + m) % 4 = (1 + 3) % 4 = 0;
  • n = 5 時,索引:((((0 + m) % 2 + 3) % 3 + m) % 4 + m) % 5 = (0 + 3) % 5 = 3 。

大致講下前面的逆推過程,找出剩余元素在前面每一輪所處的位置:

  • 當剩下 1 個數字的時候,這個數字(3)的索引為 0;
  • 往前逆推,當剩下 2 個數字的時候,在上一輪元素索引的基礎上,要補上 m 個位置,然后對數組元素個數取模,得到這一輪該元素所在的位置,代入 n,m,可得數字(3)索引為 1;
  • 當剩下 3 個數字時,同樣補上 m 個位置,然后對數組元素個數取模(這個時候數組元素個數為 3),代入 m,n,得數字(3)索引為 1;

對上面的逆推過程進行總結:從最后 1 輪往前逆推時,前面一輪的元素所處的位置為,(當前索引 + m) % 前面一輪元素個數

那么根據這個公式,用代碼進行實現。

class Solution:def lastRemaining(self, n: int, m: int) -> int:ans = 0# 最后 1 位為最終保留數字# 往前逆推,從元素個數為 2 開始for i in range(2, n + 1):# 逆推公式ans = (ans + m) % ireturn ans

測試

import org.junit.Assert;
import org.junit.Test;public class LastRemainingTest {@Testpublic void test() {LastRemaining lr = new LastRemaining();Assert.assertEquals(3, lr.lastRemaining(5, 3));Assert.assertEquals(2, lr.lastRemaining(10, 17));Assert.assertEquals(3, lr.lastRemaining2(5, 3));Assert.assertEquals(2, lr.lastRemaining2(10, 17));}}

參考

  1. LeetCode 面試題62. 圓圈中最后剩下的數字

  2. LaTex數學公式生成

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445843.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445843.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445843.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mysql數據庫老是被鎖怎么解決_Mysql數據庫全局鎖是如何引起的,如何解決?

2019-01-08 回答樂觀鎖與悲觀鎖不同的是&#xff0c;它是一種邏輯上的鎖&#xff0c;而不需要數據庫提供鎖機制來支持當數據很重要&#xff0c;回滾或重試一次需要很大的開銷時&#xff0c;需要保證操作的acid性質&#xff0c;此時應該采用悲觀鎖而當數據對即時的一致性要求不高…

我們邊吃曲奇邊聊——Cookie與Session那些事

Cookie與Session分別是什么&#xff1f; HTTP Cookie&#xff08;也叫 Web Cookie 或瀏覽器 Cookie&#xff09;是服務器發送到用戶瀏覽器并保存在本地的一小塊數據&#xff0c;它會在瀏覽器下次向同一服務器再發起請求時被攜帶并發送到服務器上。 通常&#xff0c;它用于告知…

mysql 不能添加外鍵 1215_MySQL錯誤1215:無法添加外鍵約束

我正在嘗試將新模式轉發工程到我的數據庫服務器上&#xff0c;但是我不知道為什么會收到此錯誤。我試圖在這里搜索答案&#xff0c;但是我發現的所有內容都說是將db引擎設置為Innodb或確保要用作外鍵的鍵是它們自己表中的主鍵。如果我沒記錯的話&#xff0c;我都做過這兩件事。…

JMH初體驗

什么是JMH JMH是 Java Microbenchmark Harness 的縮寫。中文意思大致是 “JAVA 微基準測試套件”。 基準測試是指通過設計科學的測試方法、測試工具和測試系統&#xff0c;實現對一類測試對象的某項性能指標進行定量的和可對比的測試。——百度百科 為什么要使用 JMH 基準測試…

java map取第一個元素_Java Set接口 Map 與枚舉

Set接口概述一個不包含重復元素的 collection。更確切地講&#xff0c;set 不包含滿足 e1.equals(e2) 的元素對 e1 和 e2&#xff0c;并且最多包含一個 null 元素特點Set接口是無序的 Set 是繼承于Collection的接口。它是一個不允許有重復元素的集合。Set可以存儲null值,但是nu…

Python中yield簡單用法

Python中yield簡單用法 你或許知道帶有yield的函數在Python中被稱之為generator&#xff0c;那何為 generator&#xff1f; 我們暫時拋開generator&#xff0c;先從一個常見編程題目開始&#xff0c;循序漸進了解yield的概念。 生成Fibonacci數列 Fibonacci數列是一個經典遞…

js 用下標獲取map值_js map方法處理返回數據,獲取指定數據簡寫方法

map方法處理返回數據&#xff0c;獲取指定數據簡寫方法前言后端返回數據為數組列表時&#xff0c;通常比較全面&#xff0c;包含了很多不需要的數據&#xff0c;可以通過 map 方法處理返回數據&#xff0c;篩選出想要的數據例如// 返回數據res [{id: 1,name: zhangsan,age: 16…

《Python Cookbook 3rd》筆記匯總

文章目錄一、數據結構二、字符串和文本三、數字、日期和時間四、迭代器與生成器五、文件與IO一、數據結構 標題關鍵詞1.1&#xff1a;拆分序列后賦值給多個變量可迭代對象、拆分賦值1.2&#xff1a;拆分任意長可迭代對象后賦值給多個變量可迭代對象、拆分賦值、星號表達式1.3&…

mysql hp ux_hp ux apa 切換

(HP-UX Only) OR - 1 heartbeat network using APA with 2 trunk members (HP-UX Only) OR - 1 heartbeat network with serial line (HP-UX Only) OR......一、 概述 HP 的 APA 軟件提供兩種網卡冗余切換模式,用以實現網絡高可用性...0x000000000000 hp_apa HP-UX 11i v3 Prer…

Python中[:]與[::]的用法

Python中[:]與[::]的用法 概述 [:]與[::]語法是通用序列操作&#xff08;Common Sequence Operations&#xff09;其中的兩個。用[:]或[::]對多數序列類型&#xff08;可變的或不可變的&#xff09;&#xff08;如字符串、列表等&#xff09;序列中元素進行截取。 [:]的用法…

mysql redis 中間件_Docker快速搭建Mysql社區版,Redis,MongoDb、MQ等等中間件。

一&#xff1a;安裝docker社區版。Centos系列(最好用7以上的版本&#xff0c;docker需要3.1以上的linux內核版本)sudo yum install docker-ce docker-ce-cli containerd.iosudo systemctl start dockersudo docker run hello-world如果你敲docker info需要root密碼&#xff0c;…

JavaScript中String的slice(),substr(),substring()三者區別

JavaScript中String的slice()&#xff0c;substr()&#xff0c;substring()三者區別 共同之處 從給定的字符串中截取片段&#xff0c;并返回全新的這片段的字符串對象&#xff0c;且不會改動原字符串。 具體不同之處 slice() str.slice(beginIndex[, endIndex])參數描述be…

pythontuple數據類型_數據類型-元組Tuple

Python Tuple用于存儲不可變python對象的序列。元組類似于列表&#xff0c;因為可以改變列表中存儲的項的值&#xff0c;而元組是不可變的&#xff0c;并且不能改變存儲在元組中的項的值。元組可以寫成用小括號括起來的逗號分隔值的集合。元組可以定義如下。T1 (101, "Ay…

《劍指Offer》24:反轉鏈表

題目 定義一個函數&#xff0c;輸入一個鏈表的頭節點&#xff0c;反轉鏈表并輸出反轉后鏈表的頭節點。鏈表節點定義如下&#xff1a; public static class ListNode{public int val;public ListNode next;public ListNode(int val) {this.val val;} }分析 方法一&#xff1…

python兩個for循環為什么第二個循環里值不變_兩個for循環,第二個只在第一個迭代python上執行...

我是一個pythonnoob&#xff0c;我試圖比較兩個文件中的行之間的值&#xff0c;如果行在第二個文件中&#xff0c;則輸出“line name”&#xff0c;然后輸出1&#xff1b;如果第二個文件中缺少該行&#xff0c;則輸出0。第一次迭代返回1&#xff0c;因為該行在第二個文件中&…

python如何問問題_學會正確的提問

可能很多讀者看到這個標題會感覺很可笑&#xff0c;提問誰不會啊&#xff0c;互聯網時代&#xff0c;提問還不是一句話的事情&#xff1f;個人、技術群、論壇里都可以提問啊&#xff0c;「你好」「在嗎&#xff1f;」「有人用過 xx 工具嗎&#xff1f;」。首先&#xff0c;提問…

如何保證接口的冪等性

如何保證接口的冪等性 什么是冪等性 冪等性是系統服務對外一種承諾&#xff0c;承諾只要調用接口成功&#xff0c;外部多次調用對系統的影響是一致的。聲明為冪等的服務會認為外部調用失敗是常態&#xff0c;并且失敗之后必然會有重試。 通俗地說&#xff0c;接口冪等性就是…

mysql二進制方式_MySQL數據庫之MySql二進制連接方式詳解

本文主要向大家介紹了MySQL數據庫之MySql二進制連接方式詳解 &#xff0c;通過具體的內容向大家展現&#xff0c;希望對大家學習MySQL數據庫有所幫助。使用mysql二進制方式連接您可以使用MySQL二進制方式進入到mysql命令提示符下來連接MySQL數據庫。實例以下是從命令行中連接my…

xposed模塊編寫教程_太極xposed模塊使用教程

今天給大家分享一下太極xposed模塊使用教程。很多小伙伴說下載不到Xposed模塊&#xff0c;這個網上其實很多&#xff0c;但是第三方的下載站就算了吧。我也是一個深受其害的網癮少年&#xff0c;只要是下載站的軟件&#xff0c;一不留心一次性電腦可能會多安裝好多個軟件&#…

如何使用mysql添加更新_Mysql 存在既更新,不存在就添加(sql語句)

討人喜歡的 MySQL replace into 用法(insert into 的增強版)在向表中插入數據的時候&#xff0c;經常遇到這樣的情況&#xff1a;1. 首先判斷數據是否存在&#xff1b; 2. 如果不存在&#xff0c;則插入&#xff1b;3.如果存在&#xff0c;則更新。在 SQL Server 中可以這樣處理…