Next lexicographical sequence

  • Given a string of lowercase English letters
  • Rearrange the characters to form a new string representing the next immediate sequence in lexicographical (alphabetical) order.
  • If the given string is already the last lexicographical arrangement possible, return the first lexicographical arrangement.

The keyword here is rearrange

  • Input : s = “abcd” => Output : “abdc”
  • Input : s = “dcba” => Output : “abcd”

It can help to assign numbers to letters (a=1, b=2, c=3, etc.)

ltr nbr
abc 123
cba 321

The point:

  • We look for the smallest suffix (right hand side) that we can rearrange to get a greater permutation
  • We focus on changing the smallest possible suffix that can produce a greater permutation
  • We search for the first character that is smaller than the next one (pivot)
  • If no pivot is found, the string is already at its maximum “value”.
  • Then, we return the reversed string (this will become the first lexicographical permutation).
  • If a pivot is found, swap it with the next larger character to its right.

Example:

  • Input: s = “a b c e d d a”
  • Pivot is ‘c’, as it’s the first character smaller than its successor ‘e’:
  • Swap pivot ‘c’ with the right most next larger character (‘d’):
  • Result: “a b d e d c a”

Notice the suffix after the pivot is “non-increasing”.

  • To minimize the suffix, reverse it:
  • Result: “a b d a c d e”

Complexity :

Time Space
O(n) O(n)
  • Time is O(n) as we traverse the string a few times:
    • Searching for pivot
    • Finding rightmost successor
    • Reversing the suffix
  • Space complexity is O(n) due to the letters[] array allocation

About Rust :

  • let mut letters: Vec<char> = s.chars().collect();
  • return letters.into_iter().rev().collect();
    • into_iter() gives ownership of the elements to the iterator
    • no copying, no additional allocations, no unnecessary clones (.cloned())
    • but letters is lost
  • let mut letters = s.as_bytes().to_vec();
    • s.as_bytes() alone would not suffice because it returns an immutable slice &[u8]
  • if pivot.is_none()
  • return String::from_utf8(letters).expect("Only ASCII chars expected")
  • YES : tested on the Rust Playground
fn next_lexicographical_seq(s:&str) -> String {
    // transform the view into a vector of chars (not a String) we can iterate on. 
    // s.chars() is an iterator, .collect() transforms the iterator in a collection. 
    let mut letters: Vec<char> = s.chars().collect(); 
    
    // Look for the pivot
    let mut pivot: Option<usize> = None;
    // length of the array - 1, but with underflow protection. If the array is empty (len() = 0), this will return 0 instead of panicking.
    for i in (0..letters.len().saturating_sub(1)).rev() { 
        if letters[i] < letters[i + 1] {
            pivot = Some(i);
            break;
        }
    }

    // if pivot NOT found the string "value" is already max
    // return the reversed string
    // .collect::<String>() : gather the elements from the iterator into a String
    // Here, Rust use the return type in the function to infer the type of .collect()
    if pivot.is_none() {
        // return letters.iter().rev().collect(); 
        // OK return letters.iter().rev().collect::<String>(); 
        // OK {
        //     reverse() act in-place
        //     letters.reverse();
        //     letters.iter().collect::<String>()
        // }
        // Better, give ownership of the elements to the iterator. 
        // Therefore : no copying, no additional allocations, no unnecessary clones
        return letters.into_iter().rev().collect(); 
    }

    // Get the value from the Option
    let pivot = pivot.unwrap(); //let pivot = pivot.expect("Pivot should exist at this point");
    
    // starting from the right hand side, find the first char "larger" than pivot
    // length of the array - 1, but with underflow protection. If the array is empty (len() = 0), this will return 0 instead of panicking.
    let mut rightmost_successor = letters.len().saturating_sub(1);
    while letters[rightmost_successor] <= letters[pivot]{
        rightmost_successor -= 1;
    }
    
    // swap pivot and rightmost successor
    letters.swap(pivot, rightmost_successor);

     // Reverse the suffix
    letters[pivot + 1..].reverse();

    // Create an Iterator<Item = &char>.
    // Rust must copy each char (via *c or clone()) to build the String
    // Returns the string. Rust use the return type in the function signature to infer the type of .collect()
    // letters[..].iter().collect::<String>()
    // letters.iter().collect()
    
    // Better, give ownership of the elements to the iterator
    // Therefore : no copying, no additional allocations, no unnecessary clones
    letters.into_iter().collect()
}

// fn main(){     // no main() if this code runs in a Jupyter cell 
    println!("{}", next_lexicographical_seq("abcedda"));  // abdacde
    println!("{}", next_lexicographical_seq("ynitsed"));  // ynsdeit
    println!("{}", next_lexicographical_seq("zyx"));      // xyz

// }
abdacde
ynsdeit
xyz

Same as above but with less comments

fn next_lexicographical_seq(s:&str) -> String {
    
    let mut letters: Vec<char> = s.chars().collect(); 

    // Look for the pivot
    let mut pivot: Option<usize> = None;
    for i in (0..letters.len().saturating_sub(1)).rev() {
        if letters[i] < letters[i + 1] {
            pivot = Some(i);
            break;
        }
    }

    // if pivot NOT found the string "value" is already max
    // return the reversed string
    if pivot.is_none() {
        return letters.into_iter().rev().collect(); 
    }

    // Get the value from the Option
    let pivot = pivot.unwrap(); 
    
    // starting from the right hand side, find the first char "larger" than pivot
    let mut rightmost_successor = letters.len().saturating_sub(1);
    while letters[rightmost_successor] <= letters[pivot]{
        rightmost_successor -= 1;
    }
    
    // swap pivot and rightmost successor
    letters.swap(pivot, rightmost_successor);

     // Reverse the suffix
    letters[pivot + 1..].reverse();

    // Returns the string. 
    letters.into_iter().collect()
}

// main(){
    println!("{}", next_lexicographical_seq("abcedda"));  // abdacde
    println!("{}", next_lexicographical_seq("ynitsed"));  // ynsdeit
    println!("{}", next_lexicographical_seq("zyx"));  // xyz

// }
abdacde
ynsdeit
xyz

Other way to get letters

  • Other way to convert the parameter into a vector of bytes
  • Line 4, s.as_bytes() would not suffice because it returns an immutable slice &[u8]
fn next_lexicographical_seq(s:&str) -> String {
    
    // let mut letters: Vec<char> = s.chars().collect(); 
    let mut letters = s.as_bytes().to_vec();
    
    // Look for the pivot
    let mut pivot: Option<usize> = None;
    // length of the array - 1, but with underflow protection. If the array is empty (len() = 0), this will return 0 instead of panicking.
    for i in (0..letters.len().saturating_sub(1)).rev() {
        if letters[i] < letters[i + 1] {
            pivot = Some(i);
            break;
        }
    }

    // if pivot NOT found the string "value" is already max
    // return the reversed string
    if pivot.is_none() {
        letters.reverse(); // In-place reverse
        return String::from_utf8(letters).expect("Only ASCII chars expected")
    }

    // Get the value from the Option
    let pivot = pivot.unwrap(); 
    
    // starting from the right hand side, find the first char "larger" than pivot
    // length of the array - 1, but with underflow protection. If the array is empty (len() = 0), this will return 0 instead of panicking.
    let mut rightmost_successor = letters.len().saturating_sub(1);
    while letters[rightmost_successor] <= letters[pivot]{
        rightmost_successor -= 1;
    }
    
    // swap pivot and rightmost successor
    letters.swap(pivot, rightmost_successor);

     // Reverse the suffix
    letters[pivot + 1..].reverse();

    // Returns the string. 
    // letters.into_iter().collect()
    String::from_utf8(letters).expect("Only ASCII chars expected")
}

// main(){
    println!("{}", next_lexicographical_seq("abcedda"));  // abdacde
    println!("{}", next_lexicographical_seq("ynitsed"));  // ynsdeit
    println!("{}", next_lexicographical_seq("zyx"));  // xyz

// }
abdacde
ynsdeit
xyz

V4

  • A more functional way?
fn next_lexicographical_seq(s: &str) -> String {
    // transform the view into a vector of chars we can iterate on. 
    // s.chars() is an iterator, .collect() transforms the iterator in a collection. 
    let mut letters: Vec<char> = s.chars().collect(); 

    // Find the pivot => last i such that letters[i] < letters[i + 1]
    // pivot is of type Option<usize> because .find() may NOT succeed (None) 
    // (0..letters.len() - 1) is a Range (std::ops::Range<usize>) which implement the Iterator Trait
    // At the end of the .find() pivot = None or Some(i)
    let maybe_index = (0..letters.len() - 1)
        .rev()
        .find(|&i| letters[i] < letters[i + 1]);

    // If "maybe_index" matches the pattern Some(zoubida), then create a local variable named "zoubida" containing the value inside the Option "maybe_index"
    if let Some(zoubida) = maybe_index {
        // pivot found
        // find the smallest char on the right of pivot but larger than letters[pivot]
        let successor = (zoubida + 1..letters.len())
            .rev()
            .find(|&i| letters[i] > letters[zoubida])
            .unwrap();

        // Swap
        letters.swap(zoubida, successor);

        // Reverse the suffix
        letters[zoubida + 1..].reverse();
    } else {
        // pivot NOT found the string "value" is already max
        // Reverse the string
        letters.reverse();
    }

    letters.into_iter().collect()
}

fn main(){
    println!("{}", next_lexicographical_seq("abcedda"));  // abdacde
    println!("{}", next_lexicographical_seq("ynitsed"));  // ynsdeit
    println!("{}", next_lexicographical_seq("zyx"));  // xyz

}    
abdacde
ynsdeit
xyz
  • Preferred solution?
// same as before with less comments
fn next_lexicographical_seq(s: &str) -> String {
    let mut letters: Vec<char> = s.chars().collect();

    // Find pivot 
    let maybe_pivot = (0..letters.len() - 1)
        .rev()
        .find(|&i| letters[i] < letters[i + 1]);

    // If pivot exists
    if let Some(pivot) = maybe_pivot {
        // find the smallest char on the right of pivot but larger than letters[pivot]
        let successor = (pivot + 1..letters.len())
            .rev()
            .find(|&i| letters[i] > letters[pivot])
            .unwrap();
        letters.swap(pivot, successor);
        letters[pivot + 1..].reverse(); // Reverse the suffix
    } else {
        letters.reverse(); // Reverse the string
    }
    letters.into_iter().collect()
}

// fn main(){
    println!("{}", next_lexicographical_seq("abcedda"));  // abdacde
    println!("{}", next_lexicographical_seq("ynitsed"));  // ynsdeit
    println!("{}", next_lexicographical_seq("zyx"));  // xyz

// }    
abdacde
ynsdeit
xyz

Back to top

Published on: Jun 22 2025 at 09:00 AM | Last updated: Jun 22 2025 at 09:00 AM

Copyright © 1964-2025 - 40tude